diff options
615 files changed, 17671 insertions, 9389 deletions
diff --git a/Android.bp b/Android.bp index f48e6feb1c14..12bc90680dd5 100644 --- a/Android.bp +++ b/Android.bp @@ -402,7 +402,7 @@ java_defaults { "ext", "unsupportedappusage", "framework-media-stubs-systemapi", - "framework_mediaprovider_stubs", + "framework-mediaprovider-stubs-systemapi", "framework-tethering", "framework-telephony-stubs", ], @@ -491,14 +491,11 @@ java_library { apex_available: ["//apex_available:platform"], visibility: [ "//frameworks/base", - // TODO(b/144149403) remove the below lines + // TODO(b/147128803) remove the below lines "//frameworks/base/apex/appsearch/framework", "//frameworks/base/apex/blobstore/framework", "//frameworks/base/apex/jobscheduler/framework", - "//frameworks/base/apex/permission/framework", "//frameworks/base/apex/statsd/service", - "//frameworks/base/telephony", - "//frameworks/opt/net/wifi/service", ], } @@ -517,9 +514,9 @@ java_library { installable: false, // this lib is a build-only library static_libs: [ "framework-minus-apex", - "framework-media-stubs-systemapi", - "framework_mediaprovider_stubs", "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs + "framework-media-stubs-systemapi", + "framework-mediaprovider-stubs-systemapi", "framework-permission-stubs-systemapi", "framework-sdkextensions-stubs-systemapi", // TODO(b/146167933): Use framework-statsd-stubs instead. @@ -656,6 +653,33 @@ gensrcs { output_extension: "srcjar", } +gensrcs { + name: "framework-cppstream-protos", + depfile: true, + + tools: [ + "aprotoc", + "protoc-gen-cppstream", + ], + + cmd: "mkdir -p $(genDir) " + + "&& $(location aprotoc) " + + " --plugin=$(location protoc-gen-cppstream) " + + " --dependency_out=$(depfile) " + + " --cppstream_out=$(genDir) " + + " -Iexternal/protobuf/src " + + " -I . " + + " $(in)", + + srcs: [ + ":ipconnectivity-proto-src", + "core/proto/**/*.proto", + "libs/incident/**/*.proto", + ], + + output_extension: "proto.h", +} + filegroup { name: "framework-annotations", srcs: [ @@ -945,7 +969,7 @@ filegroup { name: "incremental_manager_aidl", srcs: [ "core/java/android/os/incremental/IIncrementalManager.aidl", - "core/java/android/os/incremental/IIncrementalManagerNative.aidl", + "core/java/android/os/incremental/IIncrementalService.aidl", "core/java/android/os/incremental/IncrementalNewFileParams.aidl", "core/java/android/os/incremental/IncrementalSignature.aidl", ], @@ -1013,43 +1037,6 @@ aidl_interface { }, } -gensrcs { - name: "gen-platform-proto-constants", - depfile: true, - - tools: [ - "aprotoc", - "protoc-gen-cppstream", - ], - - srcs: [ - "core/proto/android/os/backtrace.proto", - "core/proto/android/os/batterytype.proto", - "core/proto/android/os/cpufreq.proto", - "core/proto/android/os/cpuinfo.proto", - "core/proto/android/os/data.proto", - "core/proto/android/os/kernelwake.proto", - "core/proto/android/os/pagetypeinfo.proto", - "core/proto/android/os/procrank.proto", - "core/proto/android/os/ps.proto", - "core/proto/android/os/system_properties.proto", - "core/proto/android/util/event_log_tags.proto", - "core/proto/android/util/log.proto", - ], - - // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool - cmd: "mkdir -p $(genDir) " + - "&& $(location aprotoc) " + - " --plugin=$(location protoc-gen-cppstream) " + - " --dependency_out=$(depfile) " + - " --cppstream_out=$(genDir) " + - " -Iexternal/protobuf/src " + - " -I . " + - " $(in)", - - output_extension: "proto.h", -} - subdirs = [ "cmds/*", diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp new file mode 100644 index 000000000000..be5072ce3d9d --- /dev/null +++ b/apct-tests/perftests/blobstore/Android.bp @@ -0,0 +1,28 @@ +// 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. + +android_test { + name: "BlobStorePerfTests", + srcs: ["src/**/*.java"], + static_libs: [ + "BlobStoreTestUtils", + "androidx.test.rules", + "androidx.annotation_annotation", + "apct-perftests-utils", + "ub-uiautomator", + ], + platform_apis: true, + test_suites: ["device-tests"], + certificate: "platform", +}
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/AndroidManifest.xml b/apct-tests/perftests/blobstore/AndroidManifest.xml new file mode 100644 index 000000000000..21d0726927af --- /dev/null +++ b/apct-tests/perftests/blobstore/AndroidManifest.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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.blob"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.blob"/> + +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/AndroidTest.xml b/apct-tests/perftests/blobstore/AndroidTest.xml new file mode 100644 index 000000000000..19456c6d81d7 --- /dev/null +++ b/apct-tests/perftests/blobstore/AndroidTest.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. +--> +<configuration description="Runs BlobStorePerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="BlobStorePerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.blob" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java new file mode 100644 index 000000000000..0208dab33746 --- /dev/null +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java @@ -0,0 +1,120 @@ +/* + * 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.perftests.blob; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.ParcelFileDescriptor; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.function.BiConsumer; + +// Copy of com.android.frameworks.perftests.am.util.AtraceUtils. TODO: avoid this duplication. +public class AtraceUtils { + private static final String TAG = "AtraceUtils"; + private static final boolean VERBOSE = true; + + private static final String ATRACE_START = "atrace --async_start -b %d -c %s"; + private static final String ATRACE_DUMP = "atrace --async_dump"; + private static final String ATRACE_STOP = "atrace --async_stop"; + private static final int DEFAULT_ATRACE_BUF_SIZE = 1024; + + private UiAutomation mAutomation; + private static AtraceUtils sUtils = null; + private boolean mStarted = false; + + private AtraceUtils(Instrumentation instrumentation) { + mAutomation = instrumentation.getUiAutomation(); + } + + public static AtraceUtils getInstance(Instrumentation instrumentation) { + if (sUtils == null) { + sUtils = new AtraceUtils(instrumentation); + } + return sUtils; + } + + /** + * @param categories The list of the categories to trace, separated with space. + */ + public void startTrace(String categories) { + synchronized (this) { + if (mStarted) { + throw new IllegalStateException("atrace already started"); + } + runShellCommand(String.format( + ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories)); + mStarted = true; + } + } + + public void stopTrace() { + synchronized (this) { + mStarted = false; + runShellCommand(ATRACE_STOP); + } + } + + private String runShellCommand(String cmd) { + try { + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * @param parser The function that can accept the buffer of atrace dump and parse it. + * @param handler The parse result handler + */ + public void performDump(TraceMarkParser parser, + BiConsumer<String, List<TraceMarkSlice>> handler) { + parser.reset(); + try { + if (VERBOSE) { + Log.i(TAG, "Collecting atrace dump..."); + } + writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser); + } catch (IOException e) { + Log.e(TAG, "Error in reading dump", e); + } + parser.forAllSlices(handler); + } + + // The given file descriptor here will be closed by this function + private void writeDataToBuf(ParcelFileDescriptor pfDescriptor, + TraceMarkParser parser) throws IOException { + InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + parser.visit(line); + } + } + } +} diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java new file mode 100644 index 000000000000..8e0ea9888e4c --- /dev/null +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -0,0 +1,146 @@ +/* + * 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.perftests.blob; + +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.perftests.utils.ManualBenchmarkState; +import android.perftests.utils.PerfManualStatusReporter; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.utils.blob.DummyBlobData; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@LargeTest +@RunWith(Parameterized.class) +public class BlobStorePerfTests { + // From frameworks/native/cmds/atrace/atrace.cpp + private static final String ATRACE_CATEGORY_SYSTEM_SERVER = "ss"; + // From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java + private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-"; + + private Context mContext; + private BlobStoreManager mBlobStoreManager; + private AtraceUtils mAtraceUtils; + private ManualBenchmarkState mState; + + @Rule + public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter(); + + @Parameterized.Parameter(0) + public int fileSizeInMb; + + @Parameterized.Parameters(name = "{0}MB") + public static Collection<Object[]> getParameters() { + return Arrays.asList(new Object[][] { + { 25 }, + { 50 }, + { 100 }, + { 200 }, + }); + } + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mBlobStoreManager = (BlobStoreManager) mContext.getSystemService( + Context.BLOB_STORE_SERVICE); + mAtraceUtils = AtraceUtils.getInstance(InstrumentationRegistry.getInstrumentation()); + mState = mPerfManualStatusReporter.getBenchmarkState(); + } + + @After + public void tearDown() { + // TODO: Add a blob_store shell command to trigger idle maintenance to avoid hardcoding + // job id like this. + // From BlobStoreConfig.IDLE_JOB_ID = 191934935. + runShellCommand("cmd jobscheduler run -f android 191934935"); + } + + @Test + public void testComputeDigest() throws Exception { + mAtraceUtils.startTrace(ATRACE_CATEGORY_SYSTEM_SERVER); + try { + final List<Long> durations = new ArrayList<>(); + final DummyBlobData blobData = prepareDataBlob(fileSizeInMb); + final TraceMarkParser parser = new TraceMarkParser( + line -> line.name.startsWith(ATRACE_COMPUTE_DIGEST_PREFIX)); + while (mState.keepRunning(durations)) { + commitBlob(blobData); + + durations.clear(); + collectDigestDurationsFromTrace(parser, durations); + // TODO: get and delete blobId before next iteration. + } + } finally { + mAtraceUtils.stopTrace(); + } + } + + private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) { + mAtraceUtils.performDump(parser, (key, slices) -> { + for (TraceMarkSlice slice : slices) { + durations.add(TimeUnit.MICROSECONDS.toNanos(slice.getDurationInMicroseconds())); + } + }); + } + + private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception { + final DummyBlobData blobData = new DummyBlobData(mContext, + fileSizeInMb * 1024 * 1024 /* bytes */); + blobData.prepare(); + return blobData; + } + + private void commitBlob(DummyBlobData blobData) throws Exception { + final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); + try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { + blobData.writeToSession(session); + final CompletableFuture<Integer> callback = new CompletableFuture<>(); + session.commit(mContext.getMainExecutor(), callback::complete); + // Ignore commit callback result. + callback.get(); + } + } + + private String runShellCommand(String cmd) { + try { + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING index cfe19a530b27..25a15371ae47 100644 --- a/apex/blobstore/TEST_MAPPING +++ b/apex/blobstore/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsBlobStoreTestCases" }, { - "name": "FrameworksServicesTests", + "name": "FrameworksMockingServicesTests", "options": [ { "include-filter": "com.android.server.blob" diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index f110b36c7e90..d339afac5c77 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -257,6 +257,11 @@ public final class BlobHandle implements Parcelable { return Base64.encodeToString(digest, Base64.NO_WRAP); } + /** @hide */ + public boolean isExpired() { + return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis(); + } + public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { 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 aba3e8cadfa3..c12e0ec8aec9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -64,9 +64,9 @@ class BlobMetadata { private final Context mContext; - public final long blobId; - public final BlobHandle blobHandle; - public final int userId; + private final long mBlobId; + private final BlobHandle mBlobHandle; + private final int mUserId; @GuardedBy("mMetadataLock") private final ArraySet<Committer> mCommitters = new ArraySet<>(); @@ -90,9 +90,21 @@ class BlobMetadata { BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { mContext = context; - this.blobId = blobId; - this.blobHandle = blobHandle; - this.userId = userId; + this.mBlobId = blobId; + this.mBlobHandle = blobHandle; + this.mUserId = userId; + } + + long getBlobId() { + return mBlobId; + } + + BlobHandle getBlobHandle() { + return mBlobHandle; + } + + int getUserId() { + return mUserId; } void addCommitter(@NonNull Committer committer) { @@ -159,7 +171,7 @@ class BlobMetadata { boolean hasLeases() { synchronized (mMetadataLock) { - return mLeasees.isEmpty(); + return !mLeasees.isEmpty(); } } @@ -196,7 +208,7 @@ class BlobMetadata { File getBlobFile() { if (mBlobFile == null) { - mBlobFile = BlobStoreConfig.getBlobFile(blobId); + mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); } return mBlobFile; } @@ -244,7 +256,7 @@ class BlobMetadata { void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout, dumpArgs.shouldDumpFull()); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("Committers:"); @@ -274,11 +286,11 @@ class BlobMetadata { void writeToXml(XmlSerializer out) throws IOException { synchronized (mMetadataLock) { - XmlUtils.writeLongAttribute(out, ATTR_ID, blobId); - XmlUtils.writeIntAttribute(out, ATTR_USER_ID, userId); + XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); + XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId); out.startTag(null, TAG_BLOB_HANDLE); - blobHandle.writeToXml(out); + mBlobHandle.writeToXml(out); out.endTag(null, TAG_BLOB_HANDLE); for (int i = 0, count = mCommitters.size(); i < count; ++i) { 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 eb414b0f11a6..ba2e559afdab 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -18,12 +18,15 @@ package com.android.server.blob; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Environment; +import android.util.Log; import android.util.Slog; import java.io.File; +import java.util.concurrent.TimeUnit; 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; @@ -32,6 +35,20 @@ class BlobStoreConfig { private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml"; + /** + * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}). + */ + public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L + /** + * Max time period (in millis) between each idle maintenance job run. + */ + public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + + /** + * Timeout in millis after which sessions with no updates will be deleted. + */ + public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7); + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java new file mode 100644 index 000000000000..460e776b9ff6 --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java @@ -0,0 +1,70 @@ +/* + * 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.blob; + +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID; +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_PERIOD_MILLIS; +import static com.android.server.blob.BlobStoreConfig.LOGV; +import static com.android.server.blob.BlobStoreConfig.TAG; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.util.Slog; + +import com.android.server.LocalServices; + +/** + * Maintenance job to clean up stale sessions and blobs. + */ +public class BlobStoreIdleJobService extends JobService { + @Override + public boolean onStartJob(final JobParameters params) { + AsyncTask.execute(() -> { + final BlobStoreManagerInternal blobStoreManagerInternal = LocalServices.getService( + BlobStoreManagerInternal.class); + blobStoreManagerInternal.onIdleMaintenance(); + jobFinished(params, false); + }); + return false; + } + + @Override + public boolean onStopJob(final JobParameters params) { + Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId() + + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason())); + return false; + } + + static void schedule(Context context) { + final JobScheduler jobScheduler = (JobScheduler) context.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + final JobInfo job = new JobInfo.Builder(IDLE_JOB_ID, + new ComponentName(context, BlobStoreIdleJobService.class)) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(IDLE_JOB_PERIOD_MILLIS) + .build(); + jobScheduler.schedule(job); + if (LOGV) { + Slog.v(TAG, "Scheduling the idle maintenance job"); + } + } +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java new file mode 100644 index 000000000000..5358245f517f --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java @@ -0,0 +1,28 @@ +/* + * 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.blob; + +/** + * BlobStoreManager local system service interface. + * + * Only for use within the system server. + */ +public abstract class BlobStoreManagerInternal { + /** + * Triggered from idle maintenance job to cleanup stale blobs and sessions. + */ + public abstract void onIdleMaintenance(); +} 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 13f095e5a503..0ba34cab6560 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,6 +28,8 @@ 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.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; @@ -61,6 +63,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.LongSparseArray; @@ -94,8 +97,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -115,6 +120,10 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>(); + // Contains all ids that are currently in use. + @GuardedBy("mBlobsLock") + private final ArraySet<Long> mKnownBlobIds = new ArraySet<>(); + private final Context mContext; private final Handler mHandler; private final Injector mInjector; @@ -151,6 +160,7 @@ public class BlobStoreManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); + LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); registerReceivers(); @@ -164,6 +174,8 @@ public class BlobStoreManagerService extends SystemService { readBlobSessionsLocked(allPackages); readBlobsInfoLocked(allPackages); } + } else if (phase == PHASE_BOOT_COMPLETED) { + BlobStoreIdleJobService.schedule(mContext); } } @@ -215,6 +227,40 @@ public class BlobStoreManagerService extends SystemService { } } + @VisibleForTesting + void addKnownIdsForTest(long... knownIds) { + synchronized (mBlobsLock) { + for (long id : knownIds) { + mKnownBlobIds.add(id); + } + } + } + + @VisibleForTesting + Set<Long> getKnownIdsForTest() { + synchronized (mBlobsLock) { + return mKnownBlobIds; + } + } + + @GuardedBy("mBlobsLock") + private void addSessionForUserLocked(BlobStoreSession session, int userId) { + getUserSessionsLocked(userId).put(session.getSessionId(), session); + mKnownBlobIds.add(session.getSessionId()); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) { + addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId)); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, + ArrayMap<BlobHandle, BlobMetadata> userBlobs) { + userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata); + mKnownBlobIds.add(blobMetadata.getBlobId()); + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { @@ -223,7 +269,11 @@ public class BlobStoreManagerService extends SystemService { final BlobStoreSession session = new BlobStoreSession(mContext, sessionId, blobHandle, callingUid, callingPackage, mSessionStateChangeListener); - getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session); + addSessionForUserLocked(session, UserHandle.getUserId(callingUid)); + if (LOGV) { + Slog.v(TAG, "Created session for " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); return sessionId; } @@ -251,7 +301,10 @@ public class BlobStoreManagerService extends SystemService { callingUid, callingPackage); session.open(); session.abandon(); - + if (LOGV) { + Slog.v(TAG, "Deleted session with id " + sessionId + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); } } @@ -286,6 +339,10 @@ public class BlobStoreManagerService extends SystemService { } blobMetadata.addLeasee(callingPackage, callingUid, descriptionResId, leaseExpiryTimeMillis); + if (LOGV) { + Slog.v(TAG, "Acquired lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -301,6 +358,10 @@ public class BlobStoreManagerService extends SystemService { + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } blobMetadata.removeLeasee(callingPackage, callingUid); + if (LOGV) { + Slog.v(TAG, "Released lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -329,6 +390,10 @@ public class BlobStoreManagerService extends SystemService { session.getSessionFile().delete(); getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); + mKnownBlobIds.remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Session is invalid; deleted " + session); + } break; case STATE_COMMITTED: session.verifyBlobData(); @@ -340,7 +405,7 @@ public class BlobStoreManagerService extends SystemService { if (blob == null) { blob = new BlobMetadata(mContext, session.getSessionId(), session.getBlobHandle(), userId); - userBlobs.put(session.getBlobHandle(), blob); + addBlobForUserLocked(blob, userBlobs); } final Committer newCommitter = new Committer(session.getOwnerPackageName(), session.getOwnerUid(), session.getBlobAccessMode()); @@ -355,6 +420,9 @@ public class BlobStoreManagerService extends SystemService { } getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Successfully committed session " + session); + } break; default: Slog.wtf(TAG, "Invalid session state: " @@ -397,6 +465,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_SESSIONS); out.endDocument(); sessionsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting sessions data"); + } } catch (Exception e) { sessionsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing sessions data", e); @@ -437,8 +508,8 @@ public class BlobStoreManagerService extends SystemService { if (userPackages != null && session.getOwnerPackageName().equals( userPackages.get(session.getOwnerUid()))) { - getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())).put( - session.getSessionId(), session); + addSessionForUserLocked(session, + UserHandle.getUserId(session.getOwnerUid())); } else { // Unknown package or the session data does not belong to this package. session.getSessionFile().delete(); @@ -446,6 +517,9 @@ public class BlobStoreManagerService extends SystemService { mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading sessions data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading sessions data", e); } @@ -479,6 +553,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_BLOBS); out.endDocument(); blobsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting blobs data"); + } } catch (Exception e) { blobsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing blobs data", e); @@ -510,18 +587,21 @@ public class BlobStoreManagerService extends SystemService { if (TAG_BLOB.equals(in.getName())) { final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); - final SparseArray<String> userPackages = allPackages.get(blobMetadata.userId); + final SparseArray<String> userPackages = allPackages.get( + blobMetadata.getUserId()); if (userPackages == null) { blobMetadata.getBlobFile().delete(); } else { - getUserBlobsLocked(blobMetadata.userId).put( - blobMetadata.blobHandle, blobMetadata); + addBlobForUserLocked(blobMetadata, blobMetadata.getUserId()); blobMetadata.removeInvalidCommitters(userPackages); blobMetadata.removeInvalidLeasees(userPackages); } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.blobId); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading blobs data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading blobs data", e); } @@ -614,6 +694,7 @@ public class BlobStoreManagerService extends SystemService { if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); indicesToRemove.add(i); } } @@ -633,6 +714,7 @@ public class BlobStoreManagerService extends SystemService { // Delete the blob if it doesn't have any active leases. if (!blobMetadata.hasLeases()) { blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); indicesToRemove.add(i); } } @@ -640,6 +722,10 @@ public class BlobStoreManagerService extends SystemService { userBlobs.removeAt(indicesToRemove.get(i)); } writeBlobsInfoAsync(); + if (LOGV) { + Slog.v(TAG, "Removed blobs data associated with pkg=" + + packageName + ", uid=" + uid); + } } } @@ -651,6 +737,7 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); } } @@ -660,9 +747,105 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userBlobs.size(); i < count; ++i) { final BlobMetadata blobMetadata = userBlobs.valueAt(i); blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + } + } + if (LOGV) { + Slog.v(TAG, "Removed blobs data in user " + userId); + } + } + } + + @GuardedBy("mBlobsLock") + @VisibleForTesting + void handleIdleMaintenanceLocked() { + // Cleanup any left over data on disk that is not part of index. + final ArrayList<Long> deletedBlobIds = new ArrayList<>(); + final ArrayList<File> filesToDelete = new ArrayList<>(); + final File blobsDir = BlobStoreConfig.getBlobsDir(); + if (blobsDir.exists()) { + for (File file : blobsDir.listFiles()) { + try { + final long id = Long.parseLong(file.getName()); + if (mKnownBlobIds.indexOf(id) < 0) { + filesToDelete.add(file); + deletedBlobIds.add(id); + } + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Error parsing the file name: " + file, e); + filesToDelete.add(file); + } + } + for (int i = 0, count = filesToDelete.size(); i < count; ++i) { + filesToDelete.get(i).delete(); + } + } + + // Cleanup any stale blobs. + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + userBlobs.entrySet().removeIf(entry -> { + final BlobHandle blobHandle = entry.getKey(); + final BlobMetadata blobMetadata = entry.getValue(); + boolean shouldRemove = false; + + // Cleanup expired data blobs. + if (blobHandle.isExpired()) { + shouldRemove = true; + } + + // Cleanup blobs with no active leases. + // TODO: Exclude blobs which were just committed. + if (!blobMetadata.hasLeases()) { + shouldRemove = true; + } + + if (shouldRemove) { + blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + deletedBlobIds.add(blobMetadata.getBlobId()); + } + return shouldRemove; + }); + } + writeBlobsInfoAsync(); + + // Cleanup any stale sessions. + final ArrayList<Integer> indicesToRemove = new ArrayList<>(); + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + indicesToRemove.clear(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final BlobStoreSession blobStoreSession = userSessions.valueAt(j); + boolean shouldRemove = false; + + // Cleanup sessions which haven't been modified in a while. + if (blobStoreSession.getSessionFile().lastModified() + < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) { + shouldRemove = true; } + + // Cleanup sessions with already expired data. + if (blobStoreSession.getBlobHandle().isExpired()) { + shouldRemove = true; + } + + if (shouldRemove) { + blobStoreSession.getSessionFile().delete(); + mKnownBlobIds.remove(blobStoreSession.getSessionId()); + indicesToRemove.add(j); + deletedBlobIds.add(blobStoreSession.getSessionId()); + } + } + for (int j = 0; j < indicesToRemove.size(); ++j) { + userSessions.removeAt(indicesToRemove.get(j)); } } + if (LOGV) { + Slog.v(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); + } + writeBlobSessionsAsync(); } void runClearAllSessions(@UserIdInt int userId) { @@ -727,10 +910,10 @@ public class BlobStoreManagerService extends SystemService { fout.increaseIndent(); for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { final BlobMetadata blobMetadata = userBlobs.valueAt(j); - if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) { + if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { continue; } - fout.println("Blob #" + blobMetadata.blobId); + fout.println("Blob #" + blobMetadata.getBlobId()); fout.increaseIndent(); blobMetadata.dump(fout, dumpArgs); fout.decreaseIndent(); @@ -742,6 +925,9 @@ public class BlobStoreManagerService extends SystemService { private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if (LOGV) { + Slog.v(TAG, "Received " + intent); + } switch (intent.getAction()) { case Intent.ACTION_PACKAGE_FULLY_REMOVED: case Intent.ACTION_PACKAGE_DATA_CLEARED: @@ -893,6 +1079,14 @@ public class BlobStoreManagerService extends SystemService { final DumpArgs dumpArgs = DumpArgs.parse(args); final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); + if (dumpArgs.shouldDumpHelp()) { + writer.println("dumpsys blob_store [options]:"); + fout.increaseIndent(); + dumpArgs.dumpArgsUsage(fout); + fout.decreaseIndent(); + return; + } + synchronized (mBlobsLock) { fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); fout.println(); @@ -926,6 +1120,7 @@ public class BlobStoreManagerService extends SystemService { private boolean mDumpOnlySelectedSections; private boolean mDumpSessions; private boolean mDumpBlobs; + private boolean mDumpHelp; public boolean shouldDumpSession(String packageName, int uid, long blobId) { if (!CollectionUtils.isEmpty(mDumpPackages) @@ -971,6 +1166,10 @@ public class BlobStoreManagerService extends SystemService { || mDumpUserIds.indexOf(userId) >= 0; } + public boolean shouldDumpHelp() { + return mDumpHelp; + } + private DumpArgs() {} public static DumpArgs parse(String[] args) { @@ -1000,6 +1199,8 @@ public class BlobStoreManagerService extends SystemService { dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); } else if ("--blob".equals(opt) || "-b".equals(opt)) { dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); + } else if ("--help".equals(opt) || "-h".equals(opt)) { + dumpArgs.mDumpHelp = true; } else { // Everything else is assumed to be blob ids. dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); @@ -1040,6 +1241,40 @@ public class BlobStoreManagerService extends SystemService { } return value; } + + private void dumpArgsUsage(IndentingPrintWriter pw) { + pw.println("--help | -h"); + printWithIndent(pw, "Dump this help text"); + pw.println("--sessions"); + printWithIndent(pw, "Dump only the sessions info"); + pw.println("--blobs"); + printWithIndent(pw, "Dump only the committed blobs info"); + pw.println("--package | -p [package-name]"); + printWithIndent(pw, "Dump blobs info associated with the given package"); + pw.println("--uid | -u [uid]"); + printWithIndent(pw, "Dump blobs info associated with the given uid"); + pw.println("--user [user-id]"); + printWithIndent(pw, "Dump blobs info in the given user"); + pw.println("--blob | -b [session-id | blob-id]"); + printWithIndent(pw, "Dump blob info corresponding to the given ID"); + pw.println("--full | -f"); + printWithIndent(pw, "Dump full unredacted blobs data"); + } + + private void printWithIndent(IndentingPrintWriter pw, String str) { + pw.increaseIndent(); + pw.println(str); + pw.decreaseIndent(); + } + } + + private class LocalService extends BlobStoreManagerInternal { + @Override + public void onIdleMaintenance() { + synchronized (mBlobsLock) { + handleIdleMaintenanceLocked(); + } + } } @VisibleForTesting 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 54a299722754..bd35b86babd8 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -21,11 +21,13 @@ import static android.app.blob.XmlTags.ATTR_PACKAGE; import static android.app.blob.XmlTags.ATTR_UID; import static android.app.blob.XmlTags.TAG_ACCESS_MODE; import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; +import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import android.annotation.BytesLong; @@ -40,6 +42,7 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.RevocableFileDescriptor; +import android.os.Trace; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; @@ -381,15 +384,22 @@ class BlobStoreSession extends IBlobStoreSession.Stub { void verifyBlobData() { byte[] actualDigest = null; try { + Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, + "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length()); actualDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); } catch (IOException | NoSuchAlgorithmException e) { Slog.e(TAG, "Error computing the digest", e); + } finally { + Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER); } synchronized (mSessionLock) { if (actualDigest != null && Arrays.equals(actualDigest, mBlobHandle.digest)) { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { + if (LOGV) { + Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest"); + } mState = STATE_VERIFIED_INVALID; sendCommitCallbackResult(COMMIT_RESULT_ERROR); } @@ -447,6 +457,16 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } } + @Override + public String toString() { + return "BlobStoreSession {" + + "id:" + mSessionId + + ",handle:" + mBlobHandle + + ",uid:" + mOwnerUid + + ",pkg:" + mOwnerPackageName + + "}"; + } + private void assertCallerIsOwner() { final int callingUid = Binder.getCallingUid(); if (callingUid != mOwnerUid) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 0bb07caf0b00..088cadba89ab 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1287,7 +1287,7 @@ public class JobInfo implements Parcelable { * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor * for content changes, you need to schedule a new JobInfo observing the same URIs * before you finish execution of the JobService handling the most recent changes. - * Following this pattern will ensure you do not lost any content changes: while your + * Following this pattern will ensure you do not lose any content changes: while your * job is running, the system will continue monitoring for content changes, and propagate * any it sees over to the next job you schedule.</p> * diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 91df09865037..23ae8afc1694 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -38,6 +38,12 @@ java_library { "android.media", ], + optimize: { + enabled: true, + shrink: true, + proguard_flags_files: ["updatable-media-proguard.flags"], + }, + installable: true, // TODO: build against stable API surface. Use core_platform for now to avoid diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index d59270c6a51b..96110e1541f8 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -15,6 +15,7 @@ */ package android.media; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.Uri; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.amr.AmrExtractor; +import com.google.android.exoplayer2.extractor.flac.FlacExtractor; import com.google.android.exoplayer2.extractor.flv.FlvExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; @@ -382,6 +384,7 @@ public final class MediaParser { * parse the input. */ @NonNull + @CheckResult private static UnrecognizedInputFormatException createForExtractors( @NonNull String... extractorNames) { StringBuilder builder = new StringBuilder(); @@ -536,7 +539,7 @@ public final class MediaParser { } } if (mExtractor == null) { - UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); + throw UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); } return true; } @@ -912,6 +915,7 @@ public final class MediaParser { extractorFactoriesByName.put("exo.Ac4Extractor", Ac4Extractor::new); extractorFactoriesByName.put("exo.AdtsExtractor", AdtsExtractor::new); extractorFactoriesByName.put("exo.AmrExtractor", AmrExtractor::new); + extractorFactoriesByName.put("exo.FlacExtractor", FlacExtractor::new); extractorFactoriesByName.put("exo.FlvExtractor", FlvExtractor::new); extractorFactoriesByName.put("exo.FragmentedMp4Extractor", FragmentedMp4Extractor::new); extractorFactoriesByName.put("exo.MatroskaExtractor", MatroskaExtractor::new); diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags new file mode 100644 index 000000000000..4e7d8422bf44 --- /dev/null +++ b/apex/media/framework/updatable-media-proguard.flags @@ -0,0 +1,2 @@ +# Keep all symbols in android.media. +-keep class android.media.* {*;} diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp index e6451cc29bc2..f2f5b321fafe 100644 --- a/apex/sdkextensions/testing/Android.bp +++ b/apex/sdkextensions/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.sdkext", visibility: [ "//system/apex/tests" ], defaults: ["com.android.sdkext-defaults"], diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index a2564212366f..253b2c17423d 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -222,12 +222,6 @@ interface IStatsd { const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04; /** - * Logs an event for binary push for module updates. - */ - oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, - in int options, in int state, in long[] experimentId); - - /** * Logs an event for watchdog rollbacks. */ oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName, diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index a1de330c300a..411482b88326 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -476,7 +476,7 @@ public final class StatsManager { /** * Registers a callback for an atom when that atom is to be pulled. The stats service will * invoke pullData in the callback when the stats service determines that this atom needs to be - * pulled. + * pulled. This method should not be called by third-party apps. * * @param atomTag The tag of the atom for this puller callback. * @param metadata Optional metadata specifying the timeout, cool down time, and @@ -485,6 +485,7 @@ public final class StatsManager { * @param executor The executor in which to run the callback. * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback) { @@ -510,11 +511,12 @@ public final class StatsManager { /** * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing - * pulls will still occur. + * pulls will still occur. This method should not be called by third-party apps. * * @param atomTag The tag of the atom of which to unregister * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { diff --git a/core/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index feeff6c47972..791073794b0e 100644 --- a/core/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -27,6 +27,7 @@ import android.content.Context; import android.os.IStatsd; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.FrameworkStatsLog; @@ -37,6 +38,7 @@ import com.android.internal.util.FrameworkStatsLog; public final class StatsLog { private static final String TAG = "StatsLog"; private static final boolean DEBUG = false; + private static final int EXPERIMENT_IDS_FIELD_ID = 1; private static IStatsd sService; @@ -152,28 +154,25 @@ public final class StatsLog { public static boolean logBinaryPushStateChanged(@NonNull String trainName, long trainVersionCode, int options, int state, @NonNull long[] experimentIds) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); - } - return false; - } - service.sendBinaryPushStateChangedAtom( - trainName, trainVersionCode, options, state, experimentIds); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Slog.d(TAG, - "Failed to connect to StatsCompanionService when logging " - + "BinaryPushStateChanged"); - } - return false; - } + ProtoOutputStream proto = new ProtoOutputStream(); + for (long id : experimentIds) { + proto.write( + ProtoOutputStream.FIELD_TYPE_INT64 + | ProtoOutputStream.FIELD_COUNT_REPEATED + | EXPERIMENT_IDS_FIELD_ID, + id); } + FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED, + trainName, + trainVersionCode, + (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, + (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0, + (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0, + state, + proto.getBytes(), + 0, + 0); + return true; } /** diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java index 4383b503bfe7..4495dc9de71e 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java @@ -38,11 +38,15 @@ public class StatsCompanion { private static final String TAG = "StatsCompanion"; private static final boolean DEBUG = false; - static void enforceStatsCompanionPermission(Context context) { + private static final int AID_STATSD = 1066; + + static void enforceStatsdCallingUid() { if (Binder.getCallingPid() == Process.myPid()) { return; } - context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); + if (Binder.getCallingUid() != AID_STATSD) { + throw new SecurityException("Not allowed to access StatsCompanion"); + } } /** @@ -114,7 +118,7 @@ public class StatsCompanion { @Override public void sendDataBroadcast(long lastReportTimeNs) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs); try { @@ -126,7 +130,7 @@ public class StatsCompanion { @Override public void sendActiveConfigsChangedBroadcast(long[] configIds) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); try { @@ -142,7 +146,7 @@ public class StatsCompanion { @Override public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent() .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 3e9a488fb5b8..a735cb8f14af 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -398,7 +398,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAnomalyAlarm(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); final long callingToken = Binder.clearCallingIdentity(); try { @@ -414,7 +414,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAnomalyAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); final long callingToken = Binder.clearCallingIdentity(); try { @@ -426,7 +426,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAlarmForSubscriberTriggering(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { Slog.d(TAG, "Setting periodic alarm in about " + (timestampMs @@ -445,7 +445,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAlarmForSubscriberTriggering() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { Slog.d(TAG, "Cancelling periodic alarm"); } @@ -459,7 +459,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setPullingAlarm(long nextPullTimeMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { Slog.d(TAG, "Setting pulling alarm in about " + (nextPullTimeMs - SystemClock.elapsedRealtime())); @@ -477,7 +477,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelPullingAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { Slog.d(TAG, "Cancelling pulling alarm"); } @@ -491,7 +491,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void statsdReady() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { Slog.d(TAG, "learned that statsdReady"); } @@ -503,7 +503,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void triggerUidSnapshot() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); synchronized (sStatsdLock) { final long token = Binder.clearCallingIdentity(); try { @@ -518,7 +518,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public boolean checkPermission(String permission, int pid, int uid) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 04d8b006f51d..c1dc584b89b4 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -171,8 +171,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, int[] additiveFields, IPullAtomCallback pullerCallback) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); @@ -187,6 +187,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.registerPullAtomCallback( callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); @@ -199,8 +200,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void unregisterPullAtomCallback(int atomTag) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); // Always remove the puller from StatsManagerService even if statsd is down. When statsd @@ -214,6 +215,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.unregisterPullAtomCallback(callingUid, atomTag); } catch (RemoteException e) { @@ -502,6 +504,13 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private void enforceRegisterStatsPullAtomPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.REGISTER_STATS_PULL_ATOM, + "Need REGISTER_STATS_PULL_ATOM permission."); + } + + /** * Clients should call this if blocking until statsd to be ready is desired * diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp index e4ab823f345a..22daa8eb6b3e 100644 --- a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp +++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp @@ -46,15 +46,15 @@ static void init() { } } -static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* data, - void* /*cookie*/) { +static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data, + void* /*cookie*/) { sNumPulls++; sleep_for(std::chrono::milliseconds(sLatencyMillis)); for (int i = 0; i < sAtomsPerPull; i++) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, atomTag); - stats_event_write_int64(event, (int64_t) sNumPulls); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, atomTag); + AStatsEvent_writeInt64(event, (int64_t) sNumPulls); + AStatsEvent_build(event); } return sPullReturnVal; } @@ -71,11 +71,12 @@ Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_registerStatsPull sLatencyMillis = latencyMillis; sAtomsPerPull = atomsPerPull; sNumPulls = 0; - pull_atom_metadata metadata = {.cool_down_ns = coolDownNs, - .timeout_ns = timeoutNs, - .additive_fields = nullptr, - .additive_fields_size = 0}; - register_stats_pull_atom_callback(sAtomTag, &pullAtomCallback, &metadata, nullptr); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, coolDownNs); + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, timeoutNs); + + AStatsManager_registerPullAtomCallback(sAtomTag, &pullAtomCallback, metadata, nullptr); + AStatsManager_PullAtomMetadata_release(metadata); } extern "C" @@ -83,6 +84,6 @@ JNIEXPORT void JNICALL Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller( JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/) { - unregister_stats_pull_atom_callback(sAtomTag); + AStatsManager_unregisterPullAtomCallback(sAtomTag); } -} // namespace
\ No newline at end of file +} // namespace diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java index dbd636d2e95c..e119b4c47604 100644 --- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java @@ -71,7 +71,6 @@ public class LibStatsPullTests { */ @Before public void setup() { -// Debug.waitForDebugger(); mContext = InstrumentationRegistry.getTargetContext(); assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull(); sPullReturnValue = StatsManager.PULL_SUCCESS; diff --git a/api/current.txt b/api/current.txt index 9b2e6d42b56f..a5a51d66375d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1876,6 +1876,7 @@ package android { ctor public R.id(); field public static final int accessibilityActionContextClick = 16908348; // 0x102003c field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045 + field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054 field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042 field public static final int accessibilityActionPageDown = 16908359; // 0x1020047 field public static final int accessibilityActionPageLeft = 16908360; // 0x1020048 @@ -2872,7 +2873,7 @@ package android.accessibilityservice { method public void onSystemActionsChanged(); method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); - method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); + method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a @@ -2887,6 +2888,13 @@ package android.accessibilityservice { field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20 field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 + field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26 + field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25 + field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22 + field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23 + field public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36; // 0x24 + field public static final int GESTURE_4_FINGER_SWIPE_UP = 33; // 0x21 + field public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; // 0x27 field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11 field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12 field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 @@ -2944,6 +2952,12 @@ package android.accessibilityservice { method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float); } + public static final class AccessibilityService.ScreenshotResult { + method @Nullable public android.graphics.ColorSpace getColorSpace(); + method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer(); + method public long getTimestamp(); + } + public static final class AccessibilityService.SoftKeyboardController { method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener); method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler); @@ -3849,7 +3863,7 @@ package android.app { method public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>); method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration); method @Deprecated public void onPictureInPictureModeChanged(boolean); - method public void onPictureInPictureRequested(); + method public boolean onPictureInPictureRequested(); method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle); method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle); method @CallSuper protected void onPostResume(); @@ -5918,6 +5932,7 @@ package android.app { method public long[] getVibrationPattern(); method public boolean hasUserSetImportance(); method public boolean hasUserSetSound(); + method public boolean isImportantConversation(); method public void setAllowBubbles(boolean); method public void setBypassDnd(boolean); method public void setConversationId(@Nullable String, @Nullable String); @@ -6030,14 +6045,19 @@ package android.app { public static class NotificationManager.Policy implements android.os.Parcelable { ctor public NotificationManager.Policy(int, int, int); ctor public NotificationManager.Policy(int, int, int, int); + ctor public NotificationManager.Policy(int, int, int, int, int); method public int describeContents(); method public static String priorityCategoriesToString(int); method public static String prioritySendersToString(int); method public static String suppressedEffectsToString(int); method public void writeToParcel(android.os.Parcel, int); + field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1 + field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2 + field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3 field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR; field public static final int PRIORITY_CATEGORY_ALARMS = 32; // 0x20 field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8 + field public static final int PRIORITY_CATEGORY_CONVERSATIONS = 256; // 0x100 field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2 field public static final int PRIORITY_CATEGORY_MEDIA = 64; // 0x40 field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4 @@ -6058,6 +6078,7 @@ package android.app { field public static final int SUPPRESSED_EFFECT_STATUS_BAR = 32; // 0x20 field public final int priorityCallSenders; field public final int priorityCategories; + field public final int priorityConversationSenders; field public final int priorityMessageSenders; field public final int suppressedVisualEffects; } @@ -6795,7 +6816,7 @@ package android.app.admin { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @Nullable public android.view.SurfaceControl onSurfaceReady(@Nullable android.os.IBinder); + method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable android.os.IBinder); } public class DevicePolicyManager { @@ -7139,6 +7160,7 @@ package android.app.admin { field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8 field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1 field public static final int LEAVE_ALL_SYSTEM_APPS_ENABLED = 16; // 0x10 + field public static final int LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK = 64; // 0x40 field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10 field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4 field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20 @@ -26790,7 +26812,6 @@ package android.media { method public abstract void onSelectRoute(@NonNull String, @NonNull String); method public abstract void onSetVolume(@NonNull String, int); method public abstract void onTransferToRoute(@NonNull String, @NonNull String); - method public abstract void onUpdateVolume(@NonNull String, int); field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; } @@ -28763,6 +28784,7 @@ package android.media.tv { field public static final String COLUMN_DESCRIPTION = "description"; field public static final String COLUMN_DISPLAY_NAME = "display_name"; field public static final String COLUMN_DISPLAY_NUMBER = "display_number"; + field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; field public static final String COLUMN_INPUT_ID = "input_id"; field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; @@ -28788,6 +28810,7 @@ package android.media.tv { field public static final String SERVICE_TYPE_AUDIO_VIDEO = "SERVICE_TYPE_AUDIO_VIDEO"; field public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER"; field public static final String TYPE_1SEG = "TYPE_1SEG"; + field public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T"; field public static final String TYPE_ATSC_C = "TYPE_ATSC_C"; field public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H"; field public static final String TYPE_ATSC_T = "TYPE_ATSC_T"; @@ -28881,6 +28904,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_STARTING_PRICE = "starting_price"; field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; @@ -28928,6 +28952,8 @@ package android.media.tv { field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number"; field @Deprecated public static final String COLUMN_EPISODE_NUMBER = "episode_number"; field public static final String COLUMN_EPISODE_TITLE = "episode_title"; + field public static final String COLUMN_EVENT_ID = "event_id"; + field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; @@ -28944,6 +28970,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final String COLUMN_TITLE = "title"; @@ -29009,6 +29036,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final String COLUMN_TITLE = "title"; @@ -29069,6 +29097,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_STARTING_PRICE = "starting_price"; field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; @@ -30110,7 +30139,7 @@ package android.net { method @NonNull public android.net.NetworkCapabilities setLinkDownstreamBandwidthKbps(int); method @NonNull public android.net.NetworkCapabilities setLinkUpstreamBandwidthKbps(int); method @NonNull public android.net.NetworkCapabilities setNetworkSpecifier(@NonNull android.net.NetworkSpecifier); - method public void setOwnerUid(int); + method @NonNull public android.net.NetworkCapabilities setOwnerUid(int); method @NonNull public android.net.NetworkCapabilities setSignalStrength(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; @@ -35671,6 +35700,7 @@ package android.os { field public static final String INCREMENTAL; field public static final int PREVIEW_SDK_INT; field public static final String RELEASE; + field @NonNull public static final String RELEASE_OR_CODENAME; field @Deprecated public static final String SDK; field public static final int SDK_INT; field public static final String SECURITY_PATCH; @@ -43775,12 +43805,14 @@ package android.service.notification { method public int getPriorityCallSenders(); method public int getPriorityCategoryAlarms(); method public int getPriorityCategoryCalls(); + method public int getPriorityCategoryConversations(); method public int getPriorityCategoryEvents(); method public int getPriorityCategoryMedia(); method public int getPriorityCategoryMessages(); method public int getPriorityCategoryReminders(); method public int getPriorityCategoryRepeatCallers(); method public int getPriorityCategorySystem(); + method public int getPriorityConversationSenders(); method public int getPriorityMessageSenders(); method public int getVisualEffectAmbient(); method public int getVisualEffectBadge(); @@ -43790,6 +43822,10 @@ package android.service.notification { method public int getVisualEffectPeek(); method public int getVisualEffectStatusBar(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1 + field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2 + field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3 + field public static final int CONVERSATION_SENDERS_UNSET = 0; // 0x0 field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenPolicy> CREATOR; field public static final int PEOPLE_TYPE_ANYONE = 1; // 0x1 field public static final int PEOPLE_TYPE_CONTACTS = 2; // 0x2 @@ -43806,6 +43842,7 @@ package android.service.notification { method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean); method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds(); method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int); + method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int); method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean); method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean); method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int); @@ -44038,8 +44075,8 @@ package android.service.voice { } public static final class AlwaysOnHotwordDetector.ModelParamRange { - method public int end(); - method public int start(); + method public int getEnd(); + method public int getStart(); } public class VoiceInteractionService extends android.app.Service { @@ -55620,6 +55657,7 @@ package android.view.accessibility { field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_IME_ENTER; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY; @@ -60796,6 +60834,7 @@ package android.widget { method public void setTypeface(@Nullable android.graphics.Typeface, int); method public void setTypeface(@Nullable android.graphics.Typeface); method public void setWidth(int); + field public static final int ACCESSIBILITY_ACTION_IME_ENTER = 16908372; // 0x1020054 field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0 field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1 } diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index b42594740433..90531b16a516 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -1,151 +1,4 @@ // Signature format: 2.0 -package android.app.timedetector { - - public final class PhoneTimeSuggestion implements android.os.Parcelable { - method public void addDebugInfo(@NonNull String); - method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); - method public int describeContents(); - method @NonNull public java.util.List<java.lang.String> getDebugInfo(); - method public int getSlotIndex(); - method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR; - } - - public static final class PhoneTimeSuggestion.Builder { - ctor public PhoneTimeSuggestion.Builder(int); - method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String); - method @NonNull public android.app.timedetector.PhoneTimeSuggestion build(); - method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>); - } - - public interface TimeDetector { - method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion); - } - -} - -package android.app.timezonedetector { - - public final class PhoneTimeZoneSuggestion implements android.os.Parcelable { - method public void addDebugInfo(@NonNull String); - method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); - method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String); - method public int describeContents(); - method @NonNull public java.util.List<java.lang.String> getDebugInfo(); - method public int getMatchType(); - method public int getQuality(); - method public int getSlotIndex(); - method @Nullable public String getZoneId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR; - field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4 - field public static final int MATCH_TYPE_NA = 0; // 0x0 - field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3 - field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2 - field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5 - field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3 - field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2 - field public static final int QUALITY_NA = 0; // 0x0 - field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1 - } - - public static final class PhoneTimeZoneSuggestion.Builder { - ctor public PhoneTimeZoneSuggestion.Builder(int); - method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String); - method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build(); - method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int); - method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int); - method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String); - } - - public interface TimeZoneDetector { - method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion); - } - -} - -package android.os { - - public final class TimestampedValue<T> implements android.os.Parcelable { - ctor public TimestampedValue(long, @Nullable T); - method public int describeContents(); - method public long getReferenceTimeMillis(); - method @Nullable public T getValue(); - method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR; - } - -} - -package android.timezone { - - public final class CountryTimeZones { - method @Nullable public android.icu.util.TimeZone getDefaultTimeZone(); - method @Nullable public String getDefaultTimeZoneId(); - method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long); - method public boolean hasUtcZone(long); - method public boolean isDefaultTimeZoneBoosted(); - method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone); - method public boolean matchesCountryCode(@NonNull String); - } - - public static final class CountryTimeZones.OffsetResult { - ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean); - method @NonNull public android.icu.util.TimeZone getTimeZone(); - method public boolean isOnlyMatch(); - } - - public static final class CountryTimeZones.TimeZoneMapping { - method @NonNull public android.icu.util.TimeZone getTimeZone(); - method @NonNull public String getTimeZoneId(); - } - - public final class TelephonyLookup { - method @NonNull public static android.timezone.TelephonyLookup getInstance(); - method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder(); - } - - public final class TelephonyNetwork { - method @NonNull public String getCountryIsoCode(); - method @NonNull public String getMcc(); - method @NonNull public String getMnc(); - } - - public final class TelephonyNetworkFinder { - method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String); - } - - public final class TimeZoneFinder { - method @Nullable public String getIanaVersion(); - method @NonNull public static android.timezone.TimeZoneFinder getInstance(); - method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String); - } - - public final class TzDataSetVersion { - method public static int currentFormatMajorVersion(); - method public static int currentFormatMinorVersion(); - method public int getFormatMajorVersion(); - method public int getFormatMinorVersion(); - method public int getRevision(); - method @NonNull public String getRulesVersion(); - method public static boolean isCompatibleWithThisDevice(android.timezone.TzDataSetVersion); - method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException; - } - - public static final class TzDataSetVersion.TzDataSetException extends java.lang.Exception { - ctor public TzDataSetVersion.TzDataSetException(String); - ctor public TzDataSetVersion.TzDataSetException(String, Throwable); - } - - public final class ZoneInfoDb { - method @NonNull public static android.timezone.ZoneInfoDb getInstance(); - method @NonNull public String getVersion(); - } - -} - package android.util { public final class Log { diff --git a/api/system-current.txt b/api/system-current.txt index dc126d82b8c5..a2de8fd11f9e 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -188,6 +188,7 @@ package android { field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; + field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; @@ -218,7 +219,6 @@ package android { field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; - field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; @@ -693,7 +693,7 @@ package android.app { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException; - method public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback); + method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; @@ -701,7 +701,7 @@ package android.app { method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException; - method public void unregisterPullAtomCallback(int); + method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int); field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; @@ -1152,6 +1152,16 @@ package android.app.backup { } +package android.app.compat { + + public final class CompatChanges { + method public static boolean isChangeEnabled(long); + method public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle); + method public static boolean isChangeEnabled(long, int); + } + +} + package android.app.contentsuggestions { public final class ClassificationsRequest implements android.os.Parcelable { @@ -1473,9 +1483,9 @@ package android.bluetooth { public final class BluetoothAdapter { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); method public boolean disableBLE(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); method public boolean enableBLE(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean factoryReset(); @@ -2099,6 +2109,10 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR; } + public static class LauncherApps.ShortcutQuery { + method @NonNull public android.content.pm.LauncherApps.ShortcutQuery setLocusIds(@Nullable java.util.List<android.content.LocusId>); + } + public class PackageInstaller { method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2 @@ -2202,6 +2216,8 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 + field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 131072; // 0x20000 + field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 262144; // 0x40000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000 field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 @@ -2285,7 +2301,7 @@ package android.content.pm { method public void onPermissionsChanged(int); } - @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_DONT_AUTO_REVOKE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { } public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -3679,17 +3695,17 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { + method public int getEnd(); + method public int getStart(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; - field public final int end; - field public final int start; } public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); - field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 - field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR; field public final int audioCapabilities; field @NonNull public final String description; @@ -4318,7 +4334,7 @@ package android.media { } public final class AudioRecordingConfiguration implements android.os.Parcelable { - method public int getClientUid(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getClientUid(); } public class HwAudioSource { @@ -6291,7 +6307,9 @@ package android.net { method @Nullable public String getSSID(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); - method public void setAdministratorUids(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.net.NetworkCapabilities setAdministratorUids(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.net.NetworkCapabilities setRequestorPackageName(@NonNull String); + method @NonNull public android.net.NetworkCapabilities setRequestorUid(int); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); method @NonNull public android.net.NetworkCapabilities setTransportInfo(@NonNull android.net.TransportInfo); field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 @@ -6343,6 +6361,8 @@ package android.net { } public class NetworkRequest implements android.os.Parcelable { + method @Nullable public String getRequestorPackageName(); + method public int getRequestorUid(); method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities); } @@ -6427,7 +6447,6 @@ package android.net { } public abstract class NetworkSpecifier { - method public void assertValidFromUid(int); method @Nullable public android.net.NetworkSpecifier redact(); method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier); } @@ -7518,9 +7537,6 @@ package android.net.wifi { method @Deprecated public boolean isNoInternetAccessExpected(); method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration); method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus); - field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0 - field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1 - field @Deprecated public static final int AP_BAND_ANY = -1; // 0xffffffff field @Deprecated public static final int INVALID_NETWORK_ID = -1; // 0xffffffff field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1 field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0 @@ -7530,7 +7546,6 @@ package android.net.wifi { field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11 field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0 field @Deprecated public boolean allowAutojoin; - field @Deprecated public int apBand; field @Deprecated public int carrierId; field @Deprecated public String creatorName; field @Deprecated public int creatorUid; @@ -7559,13 +7574,12 @@ package android.net.wifi { @Deprecated public static class WifiConfiguration.NetworkSelectionStatus { method @Deprecated public int getDisableReasonCounter(int); method @Deprecated public long getDisableTime(); + method @Deprecated public static int getMaxNetworkSelectionDisableReason(); method @Deprecated @Nullable public static String getNetworkDisableReasonString(int); method @Deprecated public int getNetworkSelectionDisableReason(); method @Deprecated public int getNetworkSelectionStatus(); method @Deprecated @NonNull public String getNetworkStatusString(); method @Deprecated public boolean hasEverConnected(); - method @Deprecated public boolean isNetworkEnabled(); - method @Deprecated public boolean isNetworkPermanentlyDisabled(); field @Deprecated public static final int DISABLED_ASSOCIATION_REJECTION = 1; // 0x1 field @Deprecated public static final int DISABLED_AUTHENTICATION_FAILURE = 2; // 0x2 field @Deprecated public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5; // 0x5 @@ -7576,7 +7590,6 @@ package android.net.wifi { field @Deprecated public static final int DISABLED_NONE = 0; // 0x0 field @Deprecated public static final int DISABLED_NO_INTERNET_PERMANENT = 6; // 0x6 field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4 - field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0 field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2 field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1 @@ -8200,7 +8213,7 @@ package android.net.wifi.wificond { ctor public NativeScanResult(); method public int describeContents(); method @NonNull public byte[] getBssid(); - method @NonNull public java.util.BitSet getCapabilities(); + method @NonNull public int getCapabilities(); method public int getFrequencyMhz(); method @NonNull public byte[] getInformationElements(); method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos(); @@ -8236,12 +8249,12 @@ package android.net.wifi.wificond { public final class PnoSettings implements android.os.Parcelable { ctor public PnoSettings(); method public int describeContents(); - method public int getIntervalMillis(); + method public long getIntervalMillis(); method public int getMin2gRssiDbm(); method public int getMin5gRssiDbm(); method public int getMin6gRssiDbm(); method @NonNull public java.util.List<android.net.wifi.wificond.PnoNetwork> getPnoNetworks(); - method public void setIntervalMillis(int); + method public void setIntervalMillis(long); method public void setMin2gRssiDbm(int); method public void setMin5gRssiDbm(int); method public void setMin6gRssiDbm(int); @@ -8266,10 +8279,10 @@ package android.net.wifi.wificond { 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); - method public boolean initialize(@NonNull Runnable); method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback); + method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback); method public boolean setupInterfaceForSoftApMode(@NonNull String); method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String); @@ -12410,7 +12423,6 @@ package android.telephony { field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff - field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto"; diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index 2f1889cea4eb..0caee6bebbda 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -64,10 +64,6 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): -HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities(): - - - IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler): Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers` @@ -200,8 +196,6 @@ MutableBareField: android.net.IpConfiguration#staticIpConfiguration: MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin: -MutableBareField: android.net.wifi.WifiConfiguration#apBand: - MutableBareField: android.net.wifi.WifiConfiguration#carrierId: MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier: diff --git a/api/test-current.txt b/api/test-current.txt index 9472ce2569d3..e1f83822609d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -183,6 +183,9 @@ package android.app { field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 + field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; + field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; + field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; @@ -416,6 +419,7 @@ package android.app { method public void setFgServiceShown(boolean); method public void setImportanceLockedByCriticalDeviceFunction(boolean); method public void setImportanceLockedByOEM(boolean); + method public void setImportantConversation(boolean); method public void setOriginalImportance(int); } @@ -885,6 +889,7 @@ package android.content.pm { method public abstract boolean arePermissionsIndividuallyControlled(); method @Nullable public String getContentCaptureServicePackageName(); method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int); + method @Nullable public String getDefaultTextClassifierPackageName(); method @Nullable public String getIncidentReportApproverPackageName(); method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle); method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); @@ -894,6 +899,7 @@ package android.content.pm { method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull public abstract String getServicesSystemSharedLibraryPackageName(); method @NonNull public abstract String getSharedSystemSharedLibraryPackageName(); + method @Nullable public String getSystemTextClassifierPackageName(); method @Nullable public String[] getTelephonyPackageNames(); method @Nullable public String getWellbeingPackageName(); method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index de1dbc90eb2d..c643b0e8800c 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -37,6 +37,10 @@ typedef uint16_t EntryId; // eeee in 0xpptteeee namespace utils { +// Returns whether the Res_value::data_type represents a dynamic or regular resource reference. +bool IsReference(uint8_t data_type); + +// Converts the Res_value::data_type to a human-readable string representation. StringPiece DataTypeToString(uint8_t data_type); struct OverlayManifestInfo { diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 407478945151..43cfec3f9cf9 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -27,6 +27,7 @@ #include "idmap2/ResourceUtils.h" using android::base::StringPrintf; +using android::idmap2::utils::IsReference; using android::idmap2::utils::ResToTypeEntryName; namespace android::idmap2 { @@ -200,8 +201,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage // Only rewrite resources defined within the overlay package to their corresponding target // resource ids at runtime. bool rewrite_overlay_reference = - (overlay_resource->dataType == Res_value::TYPE_REFERENCE || - overlay_resource->dataType == Res_value::TYPE_DYNAMIC_REFERENCE) + IsReference(overlay_resource->dataType) ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data) : false; @@ -331,8 +331,13 @@ Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_a std::unique_ptr<uint8_t[]> string_pool_data; Result<ResourceMapping> resource_mapping = {{}}; if (overlay_info.resource_mapping != 0U) { + // Use the dynamic reference table to find the assigned resource id of the map xml. + const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0); + uint32_t resource_mapping_id = overlay_info.resource_mapping; + ref_table->lookupResourceId(&resource_mapping_id); + // Load the overlay resource mappings from the file specified using android:resourcesMap. - auto asset = OpenNonAssetFromResource(overlay_info.resource_mapping, overlay_asset_manager); + auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager); if (!asset) { return Error("failed opening xml for android:resourcesMap: %s", asset.GetErrorMessage().c_str()); @@ -404,8 +409,7 @@ Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value})); - if (rewrite_overlay_reference && - (data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE)) { + if (rewrite_overlay_reference && IsReference(data_type)) { overlay_map_.insert(std::make_pair(data_value, target_resource)); } @@ -421,8 +425,7 @@ void ResourceMapping::RemoveMapping(ResourceId target_resource) { const TargetValue value = target_iter->second; target_map_.erase(target_iter); - if (value.data_type != Res_value::TYPE_REFERENCE && - value.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) { + if (!IsReference(value.data_type)) { return; } diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp index a5df746ca733..98d026bc70dc 100644 --- a/cmds/idmap2/libidmap2/ResourceUtils.cpp +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -33,6 +33,10 @@ using android::util::Utf16ToUtf8; namespace android::idmap2::utils { +bool IsReference(uint8_t data_type) { + return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE; +} + StringPiece DataTypeToString(uint8_t data_type) { switch (data_type) { case Res_value::TYPE_NULL: @@ -133,7 +137,7 @@ Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, } if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) { - if ((*result_value).dataType == Res_value::TYPE_REFERENCE) { + if (IsReference((*result_value).dataType)) { info.resource_mapping = (*result_value).data; } else { return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s", diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp index f55acee029dc..8af4037be954 100644 --- a/cmds/idmap2/tests/FileUtilsTests.cpp +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -56,12 +56,12 @@ TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) { return type == DT_REG && path.size() > 4 && path.compare(path.size() - 4, 4, ".apk") == 0; }); ASSERT_THAT(v, NotNull()); - ASSERT_EQ(v->size(), 10U); + ASSERT_EQ(v->size(), 11U); ASSERT_EQ(std::set<std::string>(v->begin(), v->end()), std::set<std::string>( {root + "/target/target.apk", root + "/target/target-no-overlayable.apk", root + "/overlay/overlay.apk", root + "/overlay/overlay-no-name.apk", - root + "/overlay/overlay-no-name-static.apk", + root + "/overlay/overlay-no-name-static.apk", root + "/overlay/overlay-shared.apk", root + "/overlay/overlay-static-1.apk", root + "/overlay/overlay-static-2.apk", root + "/signature-overlay/signature-overlay.apk", root + "/system-overlay/system-overlay.apk", diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 4bc625565144..a2c156063757 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -247,6 +247,43 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) { ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x7f020002, 0x7f02000f); } +TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { + std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk"; + + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC, + /* enforce_overlayable */ true); + ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); + auto& idmap = *idmap_result; + ASSERT_THAT(idmap, NotNull()); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1U); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_THAT(data, NotNull()); + + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_TARGET_ENTRY(target_entries[0], 0x7f010000, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00010000); + ASSERT_TARGET_ENTRY(target_entries[1], 0x7f02000c, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020000); + ASSERT_TARGET_ENTRY(target_entries[2], 0x7f02000e, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020001); + ASSERT_TARGET_ENTRY(target_entries[3], 0x7f02000f, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020002); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x00010000, 0x7f010000); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x00020000, 0x7f02000c); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x00020001, 0x7f02000e); + ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x00020002, 0x7f02000f); +} + TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) { OverlayManifestInfo info{}; info.target_package = "test.target"; diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build index b921b0d3d3ad..114b099598fa 100755 --- a/cmds/idmap2/tests/data/overlay/build +++ b/cmds/idmap2/tests/data/overlay/build @@ -51,4 +51,12 @@ aapt2 link \ -o overlay-static-2.apk \ compiled.flata +aapt2 link \ + --no-resource-removal \ + --shared-lib \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifest.xml \ + -o overlay-shared.apk \ + compiled.flata + rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk Binary files differnew file mode 100644 index 000000000000..93dcc82f9358 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp index 9e9dac14c802..94855aa0311f 100644 --- a/cmds/incident/Android.bp +++ b/cmds/incident/Android.bp @@ -26,7 +26,7 @@ cc_binary { "libcutils", "liblog", "libutils", - "libincident", + "libincidentpriv", ], static_libs: [ diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp index 64f4c667820d..f07743ec2ee6 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -44,7 +44,7 @@ cc_defaults { "src/ih_util.cpp", ], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], shared_libs: [ "libbase", diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp index 25e0328b4f38..c47526abad53 100644 --- a/cmds/incidentd/Android.bp +++ b/cmds/incidentd/Android.bp @@ -43,7 +43,7 @@ cc_binary { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], proto: { type: "lite", @@ -54,7 +54,7 @@ cc_binary { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotoutil", "libservices", @@ -98,7 +98,7 @@ cc_test { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], srcs: [ "tests/**/*.cpp", @@ -128,7 +128,7 @@ cc_test { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotobuf-cpp-full", "libprotoutil", diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index ebed4ee2b82d..7e069a6b4372 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -64,7 +64,7 @@ cc_defaults { "src/config/ConfigKey.cpp", "src/config/ConfigListener.cpp", "src/config/ConfigManager.cpp", - "src/external/GpuStatsPuller.cpp", + "src/experiment_ids.proto", "src/external/Perfetto.cpp", "src/external/PullResultReceiver.cpp", "src/external/puller_util.cpp", @@ -118,22 +118,19 @@ cc_defaults { ], static_libs: [ - "android.frameworks.stats@1.0", "libbase", "libcutils", - "liblog", "libprotoutil", "libstatslog", - "libstatssocket", + "libstatsmetadata", "libsysutils", ], shared_libs: [ "libbinder", - "libgraphicsenv", - "libhidlbase", "libincident", + "liblog", "libservices", - "libstatsmetadata", + "libstatssocket", "libutils", ], } @@ -160,7 +157,7 @@ genrule { ], } -cc_library_shared { +cc_library_static { name: "libstatsmetadata", host_supported: true, generated_sources: [ @@ -223,8 +220,6 @@ cc_binary { shared_libs: ["libgtest_prod"], - vintf_fragments: ["android.frameworks.stats@1.0-service.xml"], - init_rc: ["statsd.rc"], } @@ -277,8 +272,6 @@ cc_test { "tests/e2e/PartialBucket_e2e_test.cpp", "tests/e2e/ValueMetric_pull_e2e_test.cpp", "tests/e2e/WakelockDuration_e2e_test.cpp", - "tests/external/GpuStatsPuller_test.cpp", - "tests/external/IncidentReportArgs_test.cpp", "tests/external/puller_util_test.cpp", "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", diff --git a/cmds/statsd/android.frameworks.stats@1.0-service.xml b/cmds/statsd/android.frameworks.stats@1.0-service.xml deleted file mode 100644 index bb02f66a28b1..000000000000 --- a/cmds/statsd/android.frameworks.stats@1.0-service.xml +++ /dev/null @@ -1,11 +0,0 @@ -<manifest version="1.0" type="framework"> - <hal> - <name>android.frameworks.stats</name> - <transport>hwbinder</transport> - <version>1.0</version> - <interface> - <name>IStats</name> - <instance>default</instance> - </interface> - </hal> -</manifest> diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp index 30dfe3279829..8b687438ca27 100644 --- a/cmds/statsd/benchmark/log_event_benchmark.cpp +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -23,14 +23,14 @@ namespace os { namespace statsd { static size_t createAndParseStatsEvent(uint8_t* msg) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - stats_event_write_int32(event, 2); - stats_event_write_float(event, 2.0); - stats_event_build(event); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 2); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); memcpy(msg, buf, size); return size; } diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 879b3c3e52aa..bde15a5cdaae 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -20,13 +20,17 @@ #include "StatsLogProcessor.h" #include <android-base/file.h> +#include <cutils/multiuser.h> #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h> +#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h> #include "android-base/stringprintf.h" #include "atoms_info.h" #include "external/StatsPullerManager.h" #include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" #include "metrics/CountMetricProducer.h" +#include "StatsService.h" #include "state/StateManager.h" #include "stats_log_util.h" #include "stats_util.h" @@ -68,6 +72,10 @@ const int FIELD_ID_STRINGS = 9; // for ActiveConfigList const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1; +// for permissions checks +constexpr const char* kPermissionDump = "android.permission.DUMP"; +constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; + #define NS_PER_HOUR 3600 * NS_PER_SEC #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric" @@ -181,6 +189,115 @@ void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { } } +void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR, err4 = NO_ERROR; + string trainName = string(event->GetString(1 /*train name field id*/, &err)); + int64_t trainVersionCode = event->GetLong(2 /*train version field id*/, &err2); + int32_t state = int32_t(event->GetLong(6 /*state field id*/, &err3)); +#ifdef NEW_ENCODING_SCHEME + std::vector<uint8_t> trainExperimentIdBytes = + event->GetStorage(7 /*experiment ids field id*/, &err4); +#else + string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err4); +#endif + if (err != NO_ERROR || err2 != NO_ERROR || err3 != NO_ERROR || err4 != NO_ERROR) { + ALOGE("Failed to parse fields in binary push state changed log event"); + return; + } + ExperimentIds trainExperimentIds; +#ifdef NEW_ENCODING_SCHEME + if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(), + trainExperimentIdBytes.size())) { +#else + if (!trainExperimentIds.ParseFromString(trainExperimentIdString)) { +#endif + ALOGE("Failed to parse experimentids in binary push state changed."); + return; + } + vector<int64_t> experimentIdVector = {trainExperimentIds.experiment_id().begin(), + trainExperimentIds.experiment_id().end()}; + // Update the train info on disk and get any data the logevent is missing. + getAndUpdateTrainInfoOnDisk( + state, &trainVersionCode, &trainName, &experimentIdVector); + + std::vector<uint8_t> trainExperimentIdProto; + writeExperimentIdsToProto(experimentIdVector, &trainExperimentIdProto); + int32_t userId = multiuser_get_user_id(uid); + + event->updateValue(1 /*train name field id*/, trainName, STRING); + event->updateValue(2 /*train version field id*/, trainVersionCode, LONG); +#ifdef NEW_ENCODING_SCHEME + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); +#else + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STRING); +#endif + event->updateValue(8 /*user id field id*/, userId, INT); +} + +void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(int32_t state, + int64_t* trainVersionCode, + string* trainName, + std::vector<int64_t>* experimentIds) { + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); + + bool resetExperimentIds = false; + if (readTrainInfoSuccess) { + // Keep the old train version if we received an empty version. + if (*trainVersionCode == -1) { + *trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (*trainVersionCode != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. + resetExperimentIds = true; + } + + // Keep the old train name if we received an empty train name. + if (trainName->size() == 0) { + *trainName = trainInfoOnDisk.trainName; + } else if (*trainName != trainInfoOnDisk.trainName) { + // Reset experiment ids if we received a new valid train name. + resetExperimentIds = true; + } + + // Reset if we received a different experiment id. + if (!experimentIds->empty() && + (trainInfoOnDisk.experimentIds.empty() || + experimentIds->at(0) != trainInfoOnDisk.experimentIds[0])) { + resetExperimentIds = true; + } + } + + // Find the right experiment IDs + if (!resetExperimentIds && readTrainInfoSuccess) { + *experimentIds = trainInfoOnDisk.experimentIds; + } + + if (!experimentIds->empty()) { + int64_t firstId = experimentIds->at(0); + switch (state) { + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: + experimentIds->push_back(firstId + 1); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: + experimentIds->push_back(firstId + 2); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: + experimentIds->push_back(firstId + 3); + break; + } + } + + StorageManager::writeTrainInfo(*trainVersionCode, *trainName, state, *experimentIds); +} + + void StatsLogProcessor::resetConfigs() { std::lock_guard<std::mutex> lock(mMetricsMutex); resetConfigsLocked(getElapsedRealtimeNs()); @@ -201,6 +318,12 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); + // Hard-coded logic to update train info on disk and fill in any information + // this log event may be missing. + if (event->GetTagId() == android::util::BINARY_PUSH_STATE_CHANGED) { + onBinaryPushStateChangedEventLocked(event); + } + #ifdef VERY_VERBOSE_PRINTING if (mPrintAllLogs) { ALOGI("%s", event->ToString().c_str()); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index c569bc1e33f7..c49f2e0ec68d 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -196,6 +196,14 @@ private: // Handler over the isolated uid change event. void onIsolatedUidChangedEventLocked(const LogEvent& event); + // Handler over the binary push state changed event. + void onBinaryPushStateChangedEventLocked(LogEvent* event); + + // Updates train info on disk based on binary push state changed info and + // write disk info into parameters. + void getAndUpdateTrainInfoOnDisk(int32_t state, int64_t* trainVersionCode, + string* trainName, std::vector<int64_t>* experimentIds); + // Reset all configs. void resetConfigsLocked(const int64_t timestampNs); // Reset the specified configs. diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 8a8c1e6ff0ac..a06e59c8e409 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -70,25 +70,12 @@ static binder::Status exception(uint32_t code, const std::string& msg) { return binder::Status::fromExceptionCode(code, String8(msg.c_str())); } - static bool checkPermission(const char* permission) { - sp<IStatsCompanionService> scs = getStatsCompanionService(); - if (scs == nullptr) { - return false; - } - - bool success; pid_t pid = IPCThreadState::self()->getCallingPid(); uid_t uid = IPCThreadState::self()->getCallingUid(); - - binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success); - if (!status.isOk()) { - return false; - } - return success; + return checkPermissionForIds(permission, pid, uid); } - binder::Status checkUid(uid_t expectedUid) { uid_t uid = IPCThreadState::self()->getCallingUid(); if (uid == expectedUid || uid == AID_ROOT) { @@ -870,18 +857,8 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) dprintf(out, "Incorrect number of argument supplied\n"); return UNKNOWN_ERROR; } - android::String16 trainName = android::String16(args[1].c_str()); + string trainName = string(args[1].c_str()); int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10); - int options = 0; - if (args[3] == "1") { - options = options | IStatsd::FLAG_REQUIRE_STAGING; - } - if (args[4] == "1") { - options = options | IStatsd::FLAG_ROLLBACK_ENABLED; - } - if (args[5] == "1") { - options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - } int32_t state = atoi(args[6].c_str()); vector<int64_t> experimentIds; if (argCount == 8) { @@ -892,7 +869,10 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) } } dprintf(out, "Logging BinaryPushStateChanged\n"); - sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds); + vector<uint8_t> experimentIdBytes; + writeExperimentIdsToProto(experimentIds, &experimentIdBytes); + LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0); + mProcessor->OnLogEvent(&event); return NO_ERROR; } @@ -1313,101 +1293,6 @@ Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { return Status::ok(); } -Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkPermission(kPermissionDump)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkPermission(kPermissionUsage)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - bool resetExperimentIds = false; - int64_t trainVersionCode = trainVersionCodeIn; - std::string trainNameUtf8 = std::string(String8(trainNameIn).string()); - if (readTrainInfoSuccess) { - // Keep the old train version if we received an empty version. - if (trainVersionCodeIn == -1) { - trainVersionCode = trainInfoOnDisk.trainVersionCode; - } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) { - // Reset experiment ids if we receive a new non-empty train version. - resetExperimentIds = true; - } - - // Keep the old train name if we received an empty train name. - if (trainNameUtf8.size() == 0) { - trainNameUtf8 = trainInfoOnDisk.trainName; - } else if (trainNameUtf8 != trainInfoOnDisk.trainName) { - // Reset experiment ids if we received a new valid train name. - resetExperimentIds = true; - } - - // Reset if we received a different experiment id. - if (!experimentIdsIn.empty() && - (trainInfoOnDisk.experimentIds.empty() || - experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) { - resetExperimentIds = true; - } - } - - // Find the right experiment IDs - std::vector<int64_t> experimentIds; - if (resetExperimentIds || !readTrainInfoSuccess) { - experimentIds = experimentIdsIn; - } else { - experimentIds = trainInfoOnDisk.experimentIds; - } - - if (!experimentIds.empty()) { - int64_t firstId = experimentIds[0]; - switch (state) { - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: - experimentIds.push_back(firstId + 1); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: - experimentIds.push_back(firstId + 2); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: - experimentIds.push_back(firstId + 3); - break; - } - } - - // Flatten the experiment IDs to proto - vector<uint8_t> experimentIdsProtoBuffer; - writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); - - userid_t userId = multiuser_get_user_id(uid); - bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING; - bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED; - bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, - requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); - mProcessor->OnLogEvent(&event); - return Status::ok(); -} - Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn, const android::String16& packageNameIn, const int64_t packageVersionCodeIn, @@ -1469,7 +1354,6 @@ Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackType return Status::ok(); } - Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission @@ -1487,103 +1371,6 @@ Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experiment return Status::ok(); } -hardware::Return<void> StatsService::reportSpeakerImpedance( - const SpeakerImpedance& speakerImpedance) { - android::util::stats_write(android::util::SPEAKER_IMPEDANCE_REPORTED, - speakerImpedance.speakerLocation, speakerImpedance.milliOhms); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportHardwareFailed(const HardwareFailed& hardwareFailed) { - android::util::stats_write(android::util::HARDWARE_FAILED, int32_t(hardwareFailed.hardwareType), - hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode)); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportPhysicalDropDetected( - const PhysicalDropDetected& physicalDropDetected) { - android::util::stats_write(android::util::PHYSICAL_DROP_DETECTED, - int32_t(physicalDropDetected.confidencePctg), physicalDropDetected.accelPeak, - physicalDropDetected.freefallDuration); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportChargeCycles(const ChargeCycles& chargeCycles) { - std::vector<int32_t> buckets = chargeCycles.cycleBucket; - int initialSize = buckets.size(); - for (int i = 0; i < 10 - initialSize; i++) { - buckets.push_back(-1); // Push -1 for buckets that do not exist. - } - android::util::stats_write(android::util::CHARGE_CYCLES_REPORTED, buckets[0], buckets[1], - buckets[2], buckets[3], buckets[4], buckets[5], buckets[6], buckets[7], buckets[8], - buckets[9]); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportBatteryHealthSnapshot( - const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) { - android::util::stats_write(android::util::BATTERY_HEALTH_SNAPSHOT, - int32_t(batteryHealthSnapshotArgs.type), batteryHealthSnapshotArgs.temperatureDeciC, - batteryHealthSnapshotArgs.voltageMicroV, batteryHealthSnapshotArgs.currentMicroA, - batteryHealthSnapshotArgs.openCircuitVoltageMicroV, - batteryHealthSnapshotArgs.resistanceMicroOhm, batteryHealthSnapshotArgs.levelPercent); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportSlowIo(const SlowIo& slowIo) { - android::util::stats_write(android::util::SLOW_IO, int32_t(slowIo.operation), slowIo.count); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportBatteryCausedShutdown( - const BatteryCausedShutdown& batteryCausedShutdown) { - android::util::stats_write(android::util::BATTERY_CAUSED_SHUTDOWN, - batteryCausedShutdown.voltageMicroV); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportUsbPortOverheatEvent( - const UsbPortOverheatEvent& usbPortOverheatEvent) { - android::util::stats_write(android::util::USB_PORT_OVERHEAT_EVENT_REPORTED, - usbPortOverheatEvent.plugTemperatureDeciC, usbPortOverheatEvent.maxTemperatureDeciC, - usbPortOverheatEvent.timeToOverheat, usbPortOverheatEvent.timeToHysteresis, - usbPortOverheatEvent.timeToInactive); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportSpeechDspStat( - const SpeechDspStat& speechDspStat) { - android::util::stats_write(android::util::SPEECH_DSP_STAT_REPORTED, - speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis, - speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) { - std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName; - if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) { - ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId); - return hardware::Void(); - } - if (reverseDomainName.length() > 50) { - ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str()); - return hardware::Void(); - } - LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom); - mProcessor->OnLogEvent(&event); - - return hardware::Void(); -} - void StatsService::binderDied(const wp <IBinder>& who) { ALOGW("statscompanion service died"); StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 3bfaa9842950..82a5a5305df4 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -27,8 +27,6 @@ #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" -#include <android/frameworks/stats/1.0/IStats.h> -#include <android/frameworks/stats/1.0/types.h> #include <android/os/BnStatsd.h> #include <android/os/IPendingIntentRef.h> #include <android/os/IStatsCompanionService.h> @@ -41,7 +39,6 @@ using namespace android; using namespace android::binder; -using namespace android::frameworks::stats::V1_0; using namespace android::os; using namespace std; @@ -49,10 +46,7 @@ namespace android { namespace os { namespace statsd { -using android::hardware::Return; - class StatsService : public BnStatsd, - public IStats, public IBinder::DeathRecipient { public: StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue); @@ -193,16 +187,6 @@ public: virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; /** - * Binder call to log BinaryPushStateChanged atom. - */ - virtual Status sendBinaryPushStateChangedAtom( - const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) override; - - /** * Binder call to log WatchdogRollbackOccurred atom. */ virtual Status sendWatchdogRollbackOccurredAtom( @@ -217,61 +201,6 @@ public: */ virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); - /** - * Binder call to get SpeakerImpedance atom. - */ - virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override; - - /** - * Binder call to get HardwareFailed atom. - */ - virtual Return<void> reportHardwareFailed(const HardwareFailed& hardwareFailed) override; - - /** - * Binder call to get PhysicalDropDetected atom. - */ - virtual Return<void> reportPhysicalDropDetected( - const PhysicalDropDetected& physicalDropDetected) override; - - /** - * Binder call to get ChargeCyclesReported atom. - */ - virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override; - - /** - * Binder call to get BatteryHealthSnapshot atom. - */ - virtual Return<void> reportBatteryHealthSnapshot( - const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) override; - - /** - * Binder call to get SlowIo atom. - */ - virtual Return<void> reportSlowIo(const SlowIo& slowIo) override; - - /** - * Binder call to get BatteryCausedShutdown atom. - */ - virtual Return<void> reportBatteryCausedShutdown( - const BatteryCausedShutdown& batteryCausedShutdown) override; - - /** - * Binder call to get UsbPortOverheatEvent atom. - */ - virtual Return<void> reportUsbPortOverheatEvent( - const UsbPortOverheatEvent& usbPortOverheatEvent) override; - - /** - * Binder call to get Speech DSP state atom. - */ - virtual Return<void> reportSpeechDspStat( - const SpeechDspStat& speechDspStat) override; - - /** - * Binder call to get vendor atom. - */ - virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override; - /** IBinder::DeathRecipient */ virtual void binderDied(const wp<IBinder>& who) override; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index fccefdc07363..71afc32686f1 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -202,7 +202,8 @@ message Atom { DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"]; DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"]; WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"]; - WifiRunningStateChanged wifi_running_state_changed = 114 [(module) = "framework"]; + WifiRunningStateChanged wifi_running_state_changed = 114 + [(module) = "framework", deprecated = true]; AppCompacted app_compacted = 115 [(module) = "framework"]; NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"]; DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = @@ -390,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"]; + SdkExtensionStatus sdk_extension_status = 354; } // Pulled events will start at field 10000. @@ -1258,6 +1260,8 @@ message WifiEnabledStateChanged { } /** + * This atom is deprecated starting in R. + * * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode. * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'. * Note that Wifi Scanning is monitored separately in WifiScanStateChanged. @@ -3706,6 +3710,7 @@ message FlagFlipUpdateOccurred { /** * Potential experiment ids that goes with a train install. + * Should be kept in sync with experiment_ids.proto. */ message TrainExperimentIds { repeated int64 experiment_id = 1; @@ -8333,3 +8338,27 @@ message CellBroadcastMessageError { // Exception message (or log message) associated with the error (max 1000 chars) optional string exception_message = 2; } + +/** + * Logs when the SDK Extensions test app has polled the current version. + * This is atom ID 354. + * + * Logged from: + * vendor/google_testing/integration/packages/apps/SdkExtensionsTestApp/ + */ +message SdkExtensionStatus { + enum ApiCallStatus { + CALL_NOT_ATTEMPTED = 0; + CALL_SUCCESSFUL = 1; + CALL_FAILED = 2; + } + + optional ApiCallStatus result = 1; + + // The R extension version, i.e. android.os.ext.SdkExtension.getExtensionVersion(R). + optional int32 r_extension_version = 2; + + // A number identifying which particular symbol's call failed, if any. 0 means no missing symbol. + // "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; +} diff --git a/cmds/statsd/src/experiment_ids.proto b/cmds/statsd/src/experiment_ids.proto new file mode 100644 index 000000000000..c2036314cf58 --- /dev/null +++ b/cmds/statsd/src/experiment_ids.proto @@ -0,0 +1,29 @@ +/* + * 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.os.statsd; + +option java_package = "com.android.internal.os"; +option java_outer_classname = "ExperimentIdsProto"; + +// StatsLogProcessor uses the proto to parse experiment ids from +// BinaryPushStateChanged atoms. This needs to be in sync with +// TrainExperimentIds in atoms.proto. +message ExperimentIds { + repeated int64 experiment_id = 1; +} diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp deleted file mode 100644 index 3229ba82fe3c..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 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. - */ - -#include "GpuStatsPuller.h" - -#include <binder/IServiceManager.h> -#include <graphicsenv/GpuStatsInfo.h> -#include <graphicsenv/IGpuService.h> - -#include "logd/LogEvent.h" - -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoReader; - -GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -static sp<IGpuService> getGpuService() { - const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu")); - if (!binder) { - ALOGE("Failed to get gpu service"); - return nullptr; - } - - return interface_cast<IGpuService>(binder); -} - -static bool pullGpuStatsGlobalInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsGlobalInfo> stats; - status_t status = gpuService->getGpuStatsGlobalInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_GLOBAL_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.driverPackageName)) return false; - if (!event->write(info.driverVersionName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->write(info.driverBuildTime)) return false; - if (!event->write((int64_t)info.glLoadingCount)) return false; - if (!event->write((int64_t)info.glLoadingFailureCount)) return false; - if (!event->write((int64_t)info.vkLoadingCount)) return false; - if (!event->write((int64_t)info.vkLoadingFailureCount)) return false; - if (!event->write(info.vulkanVersion)) return false; - if (!event->write(info.cpuVulkanVersion)) return false; - if (!event->write(info.glesVersion)) return false; - if (!event->write((int64_t)info.angleLoadingCount)) return false; - if (!event->write((int64_t)info.angleLoadingFailureCount)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -static bool pullGpuStatsAppInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsAppInfo> stats; - status_t status = gpuService->getGpuStatsAppInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_APP_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.appPackageName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->writeBytes(int64VectorToProtoByteString(info.glDriverLoadingTime))) { - return false; - } - if (!event->writeBytes(int64VectorToProtoByteString(info.vkDriverLoadingTime))) { - return false; - } - if (!event->writeBytes(int64VectorToProtoByteString(info.angleDriverLoadingTime))) { - return false; - } - if (!event->write(info.cpuVulkanInUse)) return false; - if (!event->write(info.falsePrerotation)) return false; - if (!event->write(info.gles1InUse)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -bool GpuStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - const sp<IGpuService> gpuService = getGpuService(); - if (!gpuService) { - return false; - } - - switch (mTagId) { - case android::util::GPU_STATS_GLOBAL_INFO: - return pullGpuStatsGlobalInfo(gpuService, data); - case android::util::GPU_STATS_APP_INFO: - return pullGpuStatsAppInfo(gpuService, data); - default: - break; - } - - return false; -} - -static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) { - if (!proto.size()) return ""; - - std::string byteString; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != nullptr) { - const size_t toRead = reader->currentToRead(); - byteString.append((char*)reader->readBuffer(), toRead); - reader->move(toRead); - } - - if (byteString.size() != proto.size()) return ""; - - return byteString; -} - -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value) { - if (value.empty()) return ""; - - ProtoOutputStream proto; - for (const auto& ele : value) { - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - 1 /* field id */, - (long long)ele); - } - - return protoOutputStreamToByteString(proto); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/GpuStatsPuller.h b/cmds/statsd/src/external/GpuStatsPuller.h deleted file mode 100644 index 2da199c51e0f..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 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. - */ - -#pragma once - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull GpuStats from GpuService. - */ -class GpuStatsPuller : public StatsPuller { -public: - explicit GpuStatsPuller(const int tagId); - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; -}; - -// convert a int64_t vector into a byte string for proto message like: -// message RepeatedInt64Wrapper { -// repeated int64 value = 1; -// } -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 15d7e33d63fd..3ceff7529440 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -32,7 +32,6 @@ #include "../logd/LogEvent.h" #include "../stats_log_util.h" #include "../statscompanion_util.h" -#include "GpuStatsPuller.h" #include "StatsCallbackPuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -51,15 +50,6 @@ StatsPullerManager::StatsPullerManager() : kAllPullAtomInfo({ // TrainInfo. {{.atomTag = android::util::TRAIN_INFO}, new TrainInfoPuller()}, - - // GpuStatsGlobalInfo - {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO}, - new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}, - - // GpuStatsAppInfo - {{.atomTag = android::util::GPU_STATS_APP_INFO}, - new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}, - }), mNextPullTimeNs(NO_ALARM_UPDATE) { } diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 9a0693a84e65..103eb0c78bd5 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -35,6 +35,34 @@ using android::util::ProtoOutputStream; using std::string; using std::vector; +// stats_event.h socket types. Keep in sync. +/* ERRORS */ +#define ERROR_NO_TIMESTAMP 0x1 +#define ERROR_NO_ATOM_ID 0x2 +#define ERROR_OVERFLOW 0x4 +#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 +#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 + +/* TYPE IDS */ +#define INT32_TYPE 0x00 +#define INT64_TYPE 0x01 +#define STRING_TYPE 0x02 +#define LIST_TYPE 0x03 +#define FLOAT_TYPE 0x04 +#define BOOL_TYPE 0x05 +#define BYTE_ARRAY_TYPE 0x06 +#define OBJECT_TYPE 0x07 +#define KEY_VALUE_PAIRS_TYPE 0x08 +#define ATTRIBUTION_CHAIN_TYPE 0x09 +#define ERROR_TYPE 0x0F + // Msg is expected to begin at the start of the serialized atom -- it should not // include the android_log_header_t or the StatsEventTag. LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid) @@ -167,37 +195,6 @@ LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requi } LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const VendorAtom& vendorAtom) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = vendorAtom.atomId; - mLogUid = AID_STATSD; - - mValues.push_back( - FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName))); - for (int i = 0; i < (int)vendorAtom.values.size(); i++) { - switch (vendorAtom.values[i].getDiscriminator()) { - case VendorAtom::Value::hidl_discriminator::intValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].intValue()))); - break; - case VendorAtom::Value::hidl_discriminator::longValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].longValue()))); - break; - case VendorAtom::Value::hidl_discriminator::floatValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].floatValue()))); - break; - case VendorAtom::Value::hidl_discriminator::stringValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].stringValue()))); - break; - } - } -} - -LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& trainInfo) { mLogdTimestampNs = wallClockTimestampNs; mElapsedTimestampNs = elapsedTimestampNs; @@ -809,6 +806,26 @@ float LogEvent::GetFloat(size_t key, status_t* err) const { return 0.0; } +std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == STORAGE) { + return value.mValue.storage_value; + } else { + *err = BAD_TYPE; + return vector<uint8_t>(); + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return vector<uint8_t>(); +} + string LogEvent::ToString() const { string result; result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 3db26765326d..5509c093a6f6 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -18,7 +18,6 @@ #include "FieldValue.h" -#include <android/frameworks/stats/1.0/types.h> #include <android/util/ProtoOutputStream.h> #include <private/android_logger.h> #include <stats_event_list.h> @@ -27,8 +26,6 @@ #include <string> #include <vector> -using namespace android::frameworks::stats::V1_0; - namespace android { namespace os { namespace statsd { @@ -103,9 +100,6 @@ public: const std::vector<uint8_t>& experimentIds, int32_t userId); explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const VendorAtom& vendorAtom); - - explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& installTrainInfo); ~LogEvent(); @@ -144,6 +138,7 @@ public: const char* GetString(size_t key, status_t* err) const; bool GetBool(size_t key, status_t* err) const; float GetFloat(size_t key, status_t* err) const; + std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed @@ -214,6 +209,22 @@ public: return LogEvent(*this); } + template <class T> + status_t updateValue(size_t key, T& value, Type type) { + int field = getSimpleField(key); + for (auto& fieldValue : mValues) { + if (fieldValue.mField.getField() == field) { + if (fieldValue.mValue.getType() == type) { + fieldValue.mValue = Value(value); + return OK; + } else { + return BAD_TYPE; + } + } + } + return BAD_INDEX; + } + private: /** * Only use this if copy is absolutely needed. diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index 58bfeb337da4..140ef4e9cea9 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -23,7 +23,6 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> -#include <hidl/HidlTransportSupport.h> #include <utils/Looper.h> #include <stdio.h> @@ -75,8 +74,6 @@ int main(int /*argc*/, char** /*argv*/) { ps->giveThreadPoolName(); IPCThreadState::self()->disableBackgroundScheduling(true); - ::android::hardware::configureRpcThreadpool(4 /*threads*/, false /*willJoin*/); - std::shared_ptr<LogEventQueue> eventQueue = std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/); @@ -89,12 +86,6 @@ int main(int /*argc*/, char** /*argv*/) { return -1; } - auto ret = gStatsService->registerAsService(); - if (ret != ::android::OK) { - ALOGE("Failed to add service as HIDL service"); - return 1; // or handle error - } - registerSigHandler(); gStatsService->sayHiToStatsCompanion(); diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 8e0c62869932..73f640e130a8 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -21,6 +21,8 @@ #include <set> #include <utils/SystemClock.h> +#include "statscompanion_util.h" + using android::util::AtomsInfo; using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_BOOL; @@ -584,6 +586,21 @@ int64_t MillisToNano(const int64_t millis) { return millis * 1000000; } +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) { + sp<IStatsCompanionService> scs = getStatsCompanionService(); + if (scs == nullptr) { + return false; + } + + bool success; + binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success); + if (!status.isOk()) { + return false; + } + + return success; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 5fdf6e260247..aec09561b7c6 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -95,6 +95,9 @@ bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { // Returns the truncated timestamp to the nearest 5 minutes if needed. int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs); +// Checks permission for given pid and uid. +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); + inline bool isVendorPulledAtom(int atomId) { return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag; } diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp index d86e29131661..30c90b1e1f71 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -21,10 +21,8 @@ #include "packages/UidMap.h" #include "stats_log_util.h" -#include <android/os/IIncidentManager.h> -#include <android/os/IncidentReportArgs.h> #include <android/util/ProtoOutputStream.h> -#include <binder/IServiceManager.h> +#include <incident/incident_report.h> #include <vector> @@ -132,7 +130,7 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int return false; } - IncidentReportArgs incidentReport; + android::os::IncidentReportRequest incidentReport; vector<uint8_t> protoData; getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, @@ -146,30 +144,21 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int uint8_t dest; switch (config.dest()) { case IncidentdDetails_Destination_AUTOMATIC: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; break; case IncidentdDetails_Destination_EXPLICIT: - dest = android::os::PRIVACY_POLICY_EXPLICIT; + dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT; break; default: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; } incidentReport.setPrivacyPolicy(dest); - incidentReport.setReceiverPkg(config.receiver_pkg()); + incidentReport.setReceiverPackage(config.receiver_pkg()); - incidentReport.setReceiverCls(config.receiver_cls()); + incidentReport.setReceiverClass(config.receiver_cls()); - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service == nullptr) { - ALOGW("Failed to fetch incident service."); - return false; - } - VLOG("Calling incidentd %p", service.get()); - binder::Status s = service->reportIncident(incidentReport); - VLOG("Report incident status: %s", s.toString8().string()); - return s.isOk(); + return incidentReport.takeReport() == NO_ERROR; } } // namespace statsd diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 35b0396e2fb1..f624e12c9623 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -46,16 +46,16 @@ Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vec } TEST(LogEventTest, TestPrimitiveParsing) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - stats_event_write_int32(event, 10); - stats_event_write_int64(event, 0x123456789); - stats_event_write_float(event, 2.0); - stats_event_write_bool(event, true); - stats_event_build(event); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeInt64(event, 0x123456789); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_writeBool(event, true); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -90,20 +90,20 @@ TEST(LogEventTest, TestPrimitiveParsing) { EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type EXPECT_EQ(1, boolItem.mValue.int_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestStringAndByteArrayParsing) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string str = "test"; - stats_event_write_string8(event, str.c_str()); - stats_event_write_byte_array(event, (uint8_t*)str.c_str(), str.length()); - stats_event_build(event); + AStatsEvent_writeString(event, str.c_str()); + AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length()); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -127,18 +127,18 @@ TEST(LogEventTest, TestStringAndByteArrayParsing) { vector<uint8_t> expectedValue = {'t', 'e', 's', 't'}; EXPECT_EQ(expectedValue, storageItem.mValue.storage_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestEmptyString) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string empty = ""; - stats_event_write_string8(event, empty.c_str()); - stats_event_build(event); + AStatsEvent_writeString(event, empty.c_str()); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -155,18 +155,18 @@ TEST(LogEventTest, TestEmptyString) { EXPECT_EQ(Type::STRING, item.mValue.getType()); EXPECT_EQ(empty, item.mValue.str_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestByteArrayWithNullCharacter) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); uint8_t message[] = {'\t', 'e', '\0', 's', 't'}; - stats_event_write_byte_array(event, message, 5); - stats_event_build(event); + AStatsEvent_writeByteArray(event, message, 5); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -184,79 +184,12 @@ TEST(LogEventTest, TestByteArrayWithNullCharacter) { vector<uint8_t> expectedValue(message, message + 5); EXPECT_EQ(expectedValue, item.mValue.storage_value); - stats_event_release(event); -} - -TEST(LogEventTest, TestKeyValuePairs) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - - struct key_value_pair pairs[4]; - pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = 1}; - pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789}; - pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 2.0}; - string str = "test"; - pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()}; - - stats_event_write_key_value_pairs(event, pairs, 4); - stats_event_build(event); - - size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); - - LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); - EXPECT_TRUE(logEvent.isValid()); - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - - const vector<FieldValue>& values = logEvent.getValues(); - EXPECT_EQ(8, values.size()); // 2 FieldValues per key-value pair - - // Check the keys first - for (int i = 0; i < values.size() / 2; i++) { - const FieldValue& item = values[2 * i]; - int32_t depth1Pos = i + 1; - bool depth1Last = i == (values.size() / 2 - 1); - Field expectedField = getField(100, {1, depth1Pos, 1}, 2, {true, depth1Last, false}); - - EXPECT_EQ(expectedField, item.mField); - EXPECT_EQ(Type::INT, item.mValue.getType()); - EXPECT_EQ(i, item.mValue.int_value); - } - - // Check the values now - // Note: pos[2] = index of type in KeyValuePair in atoms.proto - const FieldValue& int32Item = values[1]; - Field expectedField = getField(100, {1, 1, 2}, 2, {true, false, true}); - EXPECT_EQ(expectedField, int32Item.mField); - EXPECT_EQ(Type::INT, int32Item.mValue.getType()); - EXPECT_EQ(1, int32Item.mValue.int_value); - - const FieldValue& int64Item = values[3]; - expectedField = getField(100, {1, 2, 3}, 2, {true, false, true}); - EXPECT_EQ(expectedField, int64Item.mField); - EXPECT_EQ(Type::LONG, int64Item.mValue.getType()); - EXPECT_EQ(0x123456789, int64Item.mValue.long_value); - - const FieldValue& floatItem = values[5]; - expectedField = getField(100, {1, 3, 5}, 2, {true, false, true}); - EXPECT_EQ(expectedField, floatItem.mField); - EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType()); - EXPECT_EQ(2.0, floatItem.mValue.float_value); - - const FieldValue& stringItem = values[7]; - expectedField = getField(100, {1, 4, 4}, 2, {true, true, true}); - EXPECT_EQ(expectedField, stringItem.mField); - EXPECT_EQ(Type::STRING, stringItem.mValue.getType()); - EXPECT_EQ(str, stringItem.mValue.str_value); - - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestAttributionChain) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string tag1 = "tag1"; string tag2 = "tag2"; @@ -264,11 +197,11 @@ TEST(LogEventTest, TestAttributionChain) { uint32_t uids[] = {1001, 1002}; const char* tags[] = {tag1.c_str(), tag2.c_str()}; - stats_event_write_attribution_chain(event, uids, tags, 2); - stats_event_build(event); + AStatsEvent_writeAttributionChain(event, uids, tags, 2); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -305,7 +238,7 @@ TEST(LogEventTest, TestAttributionChain) { EXPECT_EQ(Type::STRING, tag2Item.mValue.getType()); EXPECT_EQ(tag2, tag2Item.mValue.str_value); - stats_event_release(event); + AStatsEvent_release(event); } #else // NEW_ENCODING_SCHEME diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp deleted file mode 100644 index ae92705aff4c..000000000000 --- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 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. - */ - -#undef LOG_TAG -#define LOG_TAG "GpuStatsPuller_test" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - -#include <graphicsenv/GpuStatsInfo.h> -#include <log/log.h> - -#include "src/external/GpuStatsPuller.h" -#include "statslog.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -// clang-format off -static const std::string DRIVER_PACKAGE_NAME = "TEST_DRIVER"; -static const std::string DRIVER_VERSION_NAME = "TEST_DRIVER_VERSION"; -static const std::string APP_PACKAGE_NAME = "TEST_APP"; -static const int64_t TIMESTAMP_WALLCLOCK = 111; -static const int64_t TIMESTAMP_ELAPSED = 222; -static const int64_t DRIVER_VERSION_CODE = 333; -static const int64_t DRIVER_BUILD_TIME = 444; -static const int64_t GL_LOADING_COUNT = 3; -static const int64_t GL_LOADING_FAILURE_COUNT = 1; -static const int64_t VK_LOADING_COUNT = 4; -static const int64_t VK_LOADING_FAILURE_COUNT = 0; -static const int64_t ANGLE_LOADING_COUNT = 2; -static const int64_t ANGLE_LOADING_FAILURE_COUNT = 1; -static const int64_t GL_DRIVER_LOADING_TIME_0 = 555; -static const int64_t GL_DRIVER_LOADING_TIME_1 = 666; -static const int64_t VK_DRIVER_LOADING_TIME_0 = 777; -static const int64_t VK_DRIVER_LOADING_TIME_1 = 888; -static const int64_t VK_DRIVER_LOADING_TIME_2 = 999; -static const int64_t ANGLE_DRIVER_LOADING_TIME_0 = 1010; -static const int64_t ANGLE_DRIVER_LOADING_TIME_1 = 1111; -static const int32_t VULKAN_VERSION = 1; -static const int32_t CPU_VULKAN_VERSION = 2; -static const int32_t GLES_VERSION = 3; -static const bool CPU_VULKAN_IN_USE = true; -static const bool FALSE_PREROTATION = true; -static const bool GLES_1_IN_USE = true; -static const size_t NUMBER_OF_VALUES_GLOBAL = 13; -static const size_t NUMBER_OF_VALUES_APP = 8; -// clang-format on - -class MockGpuStatsPuller : public GpuStatsPuller { -public: - MockGpuStatsPuller(const int tagId, vector<std::shared_ptr<LogEvent>>* data) - : GpuStatsPuller(tagId), mData(data){}; - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override { - *data = *mData; - return true; - } - - vector<std::shared_ptr<LogEvent>>* mData; -}; - -class GpuStatsPuller_test : public ::testing::Test { -public: - GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(GpuStatsPuller_test, PullGpuStatsGlobalInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_GLOBAL_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(DRIVER_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - EXPECT_TRUE(event->write(DRIVER_BUILD_TIME)); - EXPECT_TRUE(event->write(GL_LOADING_COUNT)); - EXPECT_TRUE(event->write(GL_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VULKAN_VERSION)); - EXPECT_TRUE(event->write(CPU_VULKAN_VERSION)); - EXPECT_TRUE(event->write(GLES_VERSION)); - EXPECT_TRUE(event->write(ANGLE_LOADING_COUNT)); - EXPECT_TRUE(event->write(ANGLE_LOADING_FAILURE_COUNT)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_GLOBAL_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_GLOBAL, outData[0]->size()); - EXPECT_EQ(DRIVER_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_NAME, outData[0]->getValues()[1].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(DRIVER_BUILD_TIME, outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(GL_LOADING_COUNT, outData[0]->getValues()[4].mValue.long_value); - EXPECT_EQ(GL_LOADING_FAILURE_COUNT, outData[0]->getValues()[5].mValue.long_value); - EXPECT_EQ(VK_LOADING_COUNT, outData[0]->getValues()[6].mValue.long_value); - EXPECT_EQ(VK_LOADING_FAILURE_COUNT, outData[0]->getValues()[7].mValue.long_value); - EXPECT_EQ(VULKAN_VERSION, outData[0]->getValues()[8].mValue.int_value); - EXPECT_EQ(CPU_VULKAN_VERSION, outData[0]->getValues()[9].mValue.int_value); - EXPECT_EQ(GLES_VERSION, outData[0]->getValues()[10].mValue.int_value); - EXPECT_EQ(ANGLE_LOADING_COUNT, outData[0]->getValues()[11].mValue.long_value); - EXPECT_EQ(ANGLE_LOADING_FAILURE_COUNT, outData[0]->getValues()[12].mValue.long_value); -} - -TEST_F(GpuStatsPuller_test, PullGpuStatsAppInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_APP_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(APP_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - std::vector<int64_t> glDriverLoadingTime; - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_0); - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_1); - std::vector<int64_t> vkDriverLoadingTime; - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_0); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_1); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_2); - std::vector<int64_t> angleDriverLoadingTime; - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_0); - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_1); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(glDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime))); - EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE)); - EXPECT_TRUE(event->write(FALSE_PREROTATION)); - EXPECT_TRUE(event->write(GLES_1_IN_USE)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_APP_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_APP, outData[0]->size()); - EXPECT_EQ(APP_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(int64VectorToProtoByteString(glDriverLoadingTime), - outData[0]->getValues()[2].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(vkDriverLoadingTime), - outData[0]->getValues()[3].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime), - outData[0]->getValues()[4].mValue.str_value); - EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value); - EXPECT_EQ(FALSE_PREROTATION, outData[0]->getValues()[6].mValue.int_value); - EXPECT_EQ(GLES_1_IN_USE, outData[0]->getValues()[7].mValue.int_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp index 2576cf5b1339..a011692ee625 100644 --- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -50,11 +50,11 @@ int64_t pullTimeoutNs; int64_t pullCoolDownNs; std::thread pullThread; -stats_event* createSimpleEvent(int64_t value) { - stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, pullTagId); - stats_event_write_int64(event, value); - stats_event_build(event); +AStatsEvent* createSimpleEvent(int64_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, pullTagId); + AStatsEvent_writeInt64(event, value); + AStatsEvent_build(event); return event; } @@ -62,16 +62,16 @@ void executePull(const sp<IPullAtomResultReceiver>& resultReceiver) { // Convert stats_events into StatsEventParcels. std::vector<android::util::StatsEventParcel> parcels; for (int i = 0; i < values.size(); i++) { - stats_event* event = createSimpleEvent(values[i]); + AStatsEvent* event = createSimpleEvent(values[i]); size_t size; - uint8_t* buffer = stats_event_get_buffer(event, &size); + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); android::util::StatsEventParcel p; // vector.assign() creates a copy, but this is inevitable unless // stats_event.h/c uses a vector as opposed to a buffer. p.buffer.assign(buffer, buffer + size); parcels.push_back(std::move(p)); - stats_event_release(event); + AStatsEvent_release(event); } sleep_for(std::chrono::nanoseconds(pullDelayNs)); diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 6e1890ad63a9..db09ee9311ba 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -953,24 +953,24 @@ binder::Status FakeSubsystemSleepCallback::onPullAtom( // Convert stats_events into StatsEventParcels. std::vector<android::util::StatsEventParcel> parcels; for (int i = 1; i < 3; i++) { - stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, atomTag); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomTag); std::string subsystemName = "subsystem_name_"; subsystemName = subsystemName + std::to_string(i); - stats_event_write_string8(event, subsystemName.c_str()); - stats_event_write_string8(event, "subsystem_subname foo"); - stats_event_write_int64(event, /*count= */ i); - stats_event_write_int64(event, /*time_millis= */ i * 100); - stats_event_build(event); + AStatsEvent_writeString(event, subsystemName.c_str()); + AStatsEvent_writeString(event, "subsystem_subname foo"); + AStatsEvent_writeInt64(event, /*count= */ i); + AStatsEvent_writeInt64(event, /*time_millis= */ i * 100); + AStatsEvent_build(event); size_t size; - uint8_t* buffer = stats_event_get_buffer(event, &size); + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); android::util::StatsEventParcel p; // vector.assign() creates a copy, but this is inevitable unless // stats_event.h/c uses a vector as opposed to a buffer. p.buffer.assign(buffer, buffer + size); parcels.push_back(std::move(p)); - stats_event_release(event); + AStatsEvent_write(event); } resultReceiver->pullFinished(atomTag, /*success=*/true, parcels); return binder::Status::ok(); diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 9cf1de93e344..ace13513e39d 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -31,6 +31,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN; @@ -105,7 +112,14 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_3_FINGER_SWIPE_DOWN, GESTURE_3_FINGER_SWIPE_LEFT, GESTURE_3_FINGER_SWIPE_RIGHT, - GESTURE_3_FINGER_SWIPE_UP + GESTURE_3_FINGER_SWIPE_UP, + GESTURE_4_FINGER_DOUBLE_TAP, + GESTURE_4_FINGER_SINGLE_TAP, + GESTURE_4_FINGER_SWIPE_DOWN, + GESTURE_4_FINGER_SWIPE_LEFT, + GESTURE_4_FINGER_SWIPE_RIGHT, + GESTURE_4_FINGER_SWIPE_UP, + GESTURE_4_FINGER_TRIPLE_TAP }) @Retention(RetentionPolicy.SOURCE) public @interface GestureId {} @@ -165,6 +179,9 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP"; case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP"; case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP"; + case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP"; + case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP"; + case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP"; case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP"; case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD"; case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN"; @@ -191,6 +208,10 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT"; case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT"; case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP"; + case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN"; + case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT"; + case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT"; + case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_FINGER_SWIPE_UP"; default: return Integer.toHexString(eventType); } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 2165fb35a0e5..b65f68e177ca 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -28,7 +28,9 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; +import android.graphics.ColorSpace; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -388,6 +390,27 @@ public abstract class AccessibilityService extends Service { */ public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; + /** The user has performed a four-finger swipe up gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_UP = 33; + + /** The user has performed a four-finger swipe down gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; + + /** The user has performed a four-finger swipe left gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; + + /** The user has performed a four-finger swipe right gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36; + + /** The user has performed a four-finger single tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; + + /** The user has performed a four-finger double tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; + + /** The user has performed a four-finger triple tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; + /** * The {@link Intent} that must be declared as handled by the service. */ @@ -564,7 +587,12 @@ public abstract class AccessibilityService extends Service { private FingerprintGestureController mFingerprintGestureController; /** @hide */ - public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot"; + public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER = + "screenshot_hardwareBuffer"; + + /** @hide */ + public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID = + "screenshot_colorSpaceId"; /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. @@ -1867,8 +1895,9 @@ public abstract class AccessibilityService extends Service { } /** - * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE} - * format. + * Takes a screenshot of the specified display and returns it via an + * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer} + * to construct the bitmap from the ScreenshotResult's payload. * <p> * <strong>Note:</strong> In order to take screenshot your service has * to declare the capability to take screenshot by setting the @@ -1886,7 +1915,7 @@ public abstract class AccessibilityService extends Service { * @return {@code true} if the taking screenshot accepted, {@code false} if not. */ public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<Bitmap> callback) { + @NonNull Consumer<ScreenshotResult> callback) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null"); final IAccessibilityServiceConnection connection = @@ -1896,14 +1925,22 @@ public abstract class AccessibilityService extends Service { return false; } try { - connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> { - final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT); - final long identity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> callback.accept(screenshot)); - } finally { - Binder.restoreCallingIdentity(identity); + connection.takeScreenshot(displayId, new RemoteCallback((result) -> { + if (result == null) { + sendScreenshotResult(executor, callback, null); + return; + } + final HardwareBuffer hardwareBuffer = + result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER); + final int colorSpaceId = + result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID); + ColorSpace colorSpace = null; + if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) { + colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]); } + ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer, + colorSpace, System.currentTimeMillis()); + sendScreenshotResult(executor, callback, screenshot); })); } catch (RemoteException re) { throw new RuntimeException(re); @@ -2302,4 +2339,67 @@ public abstract class AccessibilityService extends Service { this.handler = handler; } } + + private void sendScreenshotResult(Executor executor, Consumer<ScreenshotResult> callback, + ScreenshotResult screenshot) { + final ScreenshotResult result = screenshot; + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(result)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Class including hardwareBuffer, colorSpace, and timestamp to be the result for + * {@link AccessibilityService#takeScreenshot} API. + * <p> + * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at + * {@link ColorSpace.Named}. + * </p> + */ + public static final class ScreenshotResult { + private final @NonNull HardwareBuffer mHardwareBuffer; + private final @Nullable ColorSpace mColorSpace; + private final long mTimestamp; + + private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer, + @Nullable ColorSpace colorSpace, long timestamp) { + Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null"); + mHardwareBuffer = hardwareBuffer; + mColorSpace = colorSpace; + mTimestamp = timestamp; + } + + /** + * Gets the colorSpace identifying a specific organization of colors of the screenshot. + * + * @return the colorSpace or {@code null} if the name of colorSpace isn't at + * {@link ColorSpace.Named} + */ + @Nullable + public ColorSpace getColorSpace() { + return mColorSpace; + } + + /** + * Gets the hardwareBuffer representing a memory buffer of the screenshot. + * + * @return the hardwareBuffer + */ + @NonNull + public HardwareBuffer getHardwareBuffer() { + return mHardwareBuffer; + } + + /** + * Gets the timestamp of taking the screenshot. + * + * @return the timestamp from {@link System#currentTimeMillis()} + */ + public long getTimestamp() { + return mTimestamp; + }; + } } diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java index 3b79d217c513..a821dadf4948 100644 --- a/core/java/android/accessibilityservice/GestureDescription.java +++ b/core/java/android/accessibilityservice/GestureDescription.java @@ -40,7 +40,7 @@ import java.util.List; */ public final class GestureDescription { /** Gestures may contain no more than this many strokes */ - private static final int MAX_STROKE_COUNT = 10; + private static final int MAX_STROKE_COUNT = 20; /** * Upper bound on total gesture duration. Nearly all gestures will be much shorter. @@ -194,7 +194,10 @@ public final class GestureDescription { public Builder addStroke(@NonNull StrokeDescription strokeDescription) { if (mStrokes.size() >= MAX_STROKE_COUNT) { throw new IllegalStateException( - "Attempting to add too many strokes to a gesture"); + "Attempting to add too many strokes to a gesture. Maximum is " + + MAX_STROKE_COUNT + + ", got " + + mStrokes.size()); } mStrokes.add(strokeDescription); diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 5db4dd7470d8..9177d4d27491 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -110,7 +110,5 @@ interface IAccessibilityServiceConnection { int getWindowIdForLeashToken(IBinder token); - Bitmap takeScreenshot(int displayId); - - void takeScreenshotWithCallback(int displayId, in RemoteCallback callback); + void takeScreenshot(int displayId, in RemoteCallback callback); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f31c6148f89f..642f51b6bb63 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2880,13 +2880,14 @@ public class Activity extends ContextThemeWrapper * {@link #enterPictureInPictureMode(PictureInPictureParams)} at this time. For example, the * system will call this method when the activity is being put into the background, so the app * developer might want to switch an activity into PIP mode instead.</p> + * + * @return {@code true} if the activity received this callback regardless of if it acts on it + * or not. If {@code false}, the framework will assume the app hasn't been updated to leverage + * this callback and will in turn send a legacy callback of {@link #onUserLeaveHint()} for the + * app to enter picture-in-picture mode. */ - public void onPictureInPictureRequested() { - // Previous recommendation was for apps to enter picture-in-picture in onUserLeaveHint() - // which is sent after onPause(). This new method allows the system to request the app to - // go into picture-in-picture decoupling it from life cycle events. For backwards - // compatibility we schedule the life cycle events if the app didn't override this method. - mMainThread.schedulePauseAndReturnToCurrentState(mToken); + public boolean onPictureInPictureRequested() { + return false; } void dispatchMovedToDisplay(int displayId, Configuration config) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 206c7710c12f..db9aa18dbd5a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1472,7 +1472,7 @@ public class ActivityManager { dest.writeInt(1); dest.writeString(mLabel); } - if (mIcon == null) { + if (mIcon == null || mIcon.isRecycled()) { dest.writeInt(0); } else { dest.writeInt(1); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c901d2a29821..192156726984 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3772,7 +3772,15 @@ public final class ActivityThread extends ClientTransactionHandler { return; } - r.activity.onPictureInPictureRequested(); + final boolean receivedByApp = r.activity.onPictureInPictureRequested(); + if (!receivedByApp) { + // Previous recommendation was for apps to enter picture-in-picture in + // onUserLeavingHint() for cases such as the app being put into the background. For + // backwards compatibility with apps that are not using the newer + // onPictureInPictureRequested() callback, we schedule the life cycle events needed to + // trigger onUserLeavingHint(), then we return the activity to its previous state. + schedulePauseWithUserLeaveHintAndReturnToCurrentState(r); + } } /** @@ -3780,18 +3788,7 @@ public final class ActivityThread extends ClientTransactionHandler { * return to its previous state. This allows activities that rely on onUserLeaveHint instead of * onPictureInPictureRequested to enter picture-in-picture. */ - public void schedulePauseAndReturnToCurrentState(IBinder token) { - final ActivityClientRecord r = mActivities.get(token); - if (r == null) { - Log.w(TAG, "Activity to request pause with user leaving hint to no longer exists"); - return; - } - - if (r.mIsUserLeaving) { - // The activity is about to perform user leaving, so there's no need to cycle ourselves. - return; - } - + private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) { final int prevState = r.getLifecycleState(); if (prevState != ON_RESUME && prevState != ON_PAUSE) { return; @@ -4544,7 +4541,6 @@ public final class ActivityThread extends ClientTransactionHandler { if (r != null) { if (userLeaving) { performUserLeavingActivity(r); - r.mIsUserLeaving = false; } r.activity.mConfigChangeFlags |= configChanges; @@ -4559,7 +4555,6 @@ public final class ActivityThread extends ClientTransactionHandler { } final void performUserLeavingActivity(ActivityClientRecord r) { - r.mIsUserLeaving = true; mInstrumentation.callActivityOnPictureInPictureRequested(r.activity); mInstrumentation.callActivityOnUserLeaving(r.activity); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 46f86690a753..6ef99a3def92 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -27,6 +27,8 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.usage.UsageStatsManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -88,14 +90,31 @@ import java.util.function.Consumer; import java.util.function.Supplier; /** - * API for interacting with "application operation" tracking. + * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined + * by the system and cannot be amended by apps. Only system apps can change appop-modes. * - * <p>This API is not generally intended for third party application developers; most - * features are only available to system applications. + * <p>Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can + * only be read by system components. + * + * <p>Installed apps can usually only listen to changes and events on their own ops. E.g. + * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or + * {@link #startOp} for an op belonging to the app. */ @SystemService(Context.APP_OPS_SERVICE) public class AppOpsManager { /** + * This is a subtle behavior change to {@link #startWatchingMode}. + * + * Before this change the system called back for the switched op. After the change the system + * will call back for the actually requested op or all switched ops if no op is specified. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L; + + /** * <p>App ops allows callers to:</p> * * <ul> @@ -142,6 +161,38 @@ public class AppOpsManager { static IBinder sClientId; + /** + * How many seconds we want for a drop in uid state from top to settle before applying it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; + + /** + * How many second we want for a drop in uid state from foreground to settle before applying it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = + "fg_service_state_settle_time"; + + /** + * How many seconds we want for a drop in uid state from background to settle before applying + * it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = { @@ -2803,7 +2854,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessForegroundTime(int) * @see #getLastAccessBackgroundTime(int) @@ -2820,7 +2871,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access * * @see #getLastAccessTime(int) * @see #getLastAccessBackgroundTime(int) @@ -2838,7 +2889,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -2855,7 +2906,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last access event of {@code null} + * @return the last access event of {@code null} if there was no access */ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -2870,7 +2921,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -2893,7 +2944,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectForegroundTime(int) * @see #getLastRejectBackgroundTime(int) @@ -2910,7 +2961,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection * * @see #getLastRejectTime(int) * @see #getLastRejectBackgroundTime(int) @@ -2928,7 +2979,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2945,8 +2996,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * @return the last rejection event of {@code null} if there was no rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2965,7 +3015,8 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the last access time (in milliseconds since epoch) or {@code -1} + * @return the last access time (in milliseconds since epoch) or {@code -1} if there was no + * rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2988,7 +3039,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no rejection * * @see #getLastForegroundDuration(int) * @see #getLastBackgroundDuration(int) @@ -3004,7 +3055,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no foreground rejection * * @see #getLastDuration(int) * @see #getLastBackgroundDuration(int) @@ -3021,7 +3072,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no background rejection * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3040,7 +3091,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no rejection * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3064,7 +3115,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastForegroundProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3081,7 +3132,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3099,7 +3150,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy background access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3119,7 +3170,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy foreground access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3375,7 +3426,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessForegroundTime(int) * @see #getLastAccessBackgroundTime(int) @@ -3392,7 +3443,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access * * @see #getLastAccessTime(int) * @see #getLastAccessBackgroundTime(int) @@ -3410,7 +3461,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -3427,7 +3478,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last access event of {@code null} + * @return the last access event of {@code null} if there was no access */ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -3453,7 +3504,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -3489,7 +3540,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectForegroundTime(int) * @see #getLastRejectBackgroundTime(int) @@ -3506,7 +3557,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection * * @see #getLastRejectTime(int) * @see #getLastRejectBackgroundTime(int) @@ -3524,7 +3575,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -3541,7 +3592,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last reject event of {@code null} + * @return the last reject event of {@code null} if there was no rejection */ private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -3567,7 +3618,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -3611,7 +3662,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no access * * @see #getLastForegroundDuration(int) * @see #getLastBackgroundDuration(int) @@ -3627,7 +3678,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no foreground access * * @see #getLastDuration(int) * @see #getLastBackgroundDuration(int) @@ -3644,7 +3695,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no background access * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3663,7 +3714,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no access * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3738,7 +3789,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastForegroundProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3755,7 +3806,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no foreground proxy access * * @see #getLastProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3773,7 +3824,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no background proxy access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3793,7 +3844,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -6774,6 +6825,9 @@ public class AppOpsManager { * succeeds, the last execution time of the operation for this app will be updated to * the current time. * + * <p>If this is a check that is not preceding the protected operation, use + * {@link #unsafeCheckOp} instead. + * * @param op The operation to note. One of the OPSTR_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -6798,6 +6852,9 @@ public class AppOpsManager { * succeeds, the last execution time of the operation for this app will be updated to * the current time. * + * <p>If this is a check that is not preceding the protected operation, use + * {@link #unsafeCheckOp} instead. + * * @param op The operation to note. One of the OP_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -7757,9 +7814,38 @@ public class AppOpsManager { /** * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted - * appops. + * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback + * methods of this object is called. * * <p><b>Only appops related to dangerous permissions are collected.</b> + * + * <pre> + * setNotedAppOpsCollector(new AppOpsCollector() { + * ArraySet<Pair<String, String>> opsNotedForThisProcess = new ArraySet<>(); + * + * private synchronized void addAccess(String op, String accessLocation) { + * // Ops are often noted when permission protected APIs were called. + * // In this case permissionToOp() allows to resolve the permission<->op + * opsNotedForThisProcess.add(new Pair(accessType, accessLocation)); + * } + * + * public void onNoted(SyncNotedAppOp op) { + * // Accesses is currently happening, hence stack trace describes location of access + * addAccess(op.getOp(), Arrays.toString(Thread.currentThread().getStackTrace())); + * } + * + * public void onSelfNoted(SyncNotedAppOp op) { + * onNoted(op); + * } + * + * public void onAsyncNoted(AsyncNotedAppOp asyncOp) { + * // Stack trace is not useful for async ops as accessed happened on different thread + * addAccess(asyncOp.getOp(), asyncOp.getMessage()); + * } + * }); + * </pre> + * + * @see #setNotedAppOpsCollector */ public abstract static class AppOpsCollector { /** Callback registered with the system. This will receive the async notes ops */ diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 71cb4a403365..cd05e2c948b1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -3229,18 +3229,18 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public String getSystemTextClassifierPackageName() { + public String getDefaultTextClassifierPackageName() { try { - return mPM.getSystemTextClassifierPackageName(); + return mPM.getDefaultTextClassifierPackageName(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } @Override - public String[] getSystemTextClassifierPackages() { + public String getSystemTextClassifierPackageName() { try { - return mPM.getSystemTextClassifierPackages(); + return mPM.getSystemTextClassifierPackageName(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 4b1ba0278682..16c0910f1273 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,6 +48,8 @@ interface INotificationManager void clearData(String pkg, int uid, boolean fromApp); void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback); void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); + // TODO(b/144152069): Remove this after assessing impact on dogfood. + void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 168f782d02a6..bfc42ef8848d 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -31,8 +31,19 @@ interface ITaskOrganizerController { */ void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode); - /** Apply multiple WindowContainer operations at once. */ - void applyContainerTransaction(in WindowContainerTransaction t); + /** + * Apply multiple WindowContainer operations at once. + * @param organizer If non-null this transaction will use the synchronization + * scheme described in BLASTSyncEngine.java. The SurfaceControl transaction + * containing the effects of this WindowContainer transaction will be passed + * to the organizers Transaction ready callback. If null the transaction + * will apply with non particular synchronization constraints (other than + * it will all apply at once). + * @return If organizer was non-null returns an ID for the sync operation which will + * later be passed to transactionReady. This lets TaskOrganizer implementations + * differentiate overlapping sync operations. + */ + int applyContainerTransaction(in WindowContainerTransaction t, ITaskOrganizer organizer); /** Creates a persistent root task in WM for a particular windowing-mode. */ ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 80ba464851e0..8c3180b400ef 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -39,6 +39,7 @@ interface IUiAutomationConnection { boolean injectInputEvent(in InputEvent event, boolean sync); void syncInputTransactions(); boolean setRotation(int rotation); + Bitmap takeScreenshot(in Rect crop, int rotation); boolean clearWindowContentFrameStats(int windowId); WindowContentFrameStats getWindowContentFrameStats(int windowId); void clearWindowAnimationFrameStats(); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 4b24e098b3c6..7212be82d1a9 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -31,7 +31,6 @@ import android.os.Parcelable; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.text.TextUtils; -import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; @@ -105,6 +104,7 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_ORIG_IMP = "orig_imp"; private static final String ATT_PARENT_CHANNEL = "parent"; private static final String ATT_CONVERSATION_ID = "conv_id"; + private static final String ATT_IMP_CONVERSATION = "imp_conv"; private static final String ATT_DEMOTE = "dem"; private static final String DELIMITER = ","; @@ -196,6 +196,7 @@ public final class NotificationChannel implements Parcelable { private String mParentId = null; private String mConversationId = null; private boolean mDemoted = false; + private boolean mImportantConvo = false; /** * Creates a notification channel. @@ -263,6 +264,7 @@ public final class NotificationChannel implements Parcelable { mParentId = in.readString(); mConversationId = in.readString(); mDemoted = in.readBoolean(); + mImportantConvo = in.readBoolean(); } @Override @@ -321,6 +323,7 @@ public final class NotificationChannel implements Parcelable { dest.writeString(mParentId); dest.writeString(mConversationId); dest.writeBoolean(mDemoted); + dest.writeBoolean(mImportantConvo); } /** @@ -354,6 +357,14 @@ public final class NotificationChannel implements Parcelable { } /** + * @hide + */ + @TestApi + public void setImportantConversation(boolean importantConvo) { + mImportantConvo = importantConvo; + } + + /** * Allows users to block notifications sent through this channel, if this channel belongs to * a package that is signed with the system signature. If the channel does not belong to a * package that is signed with the system signature, this method does nothing. @@ -601,6 +612,18 @@ public final class NotificationChannel implements Parcelable { } /** + * Whether or not notifications in this conversation are considered important. + * + * <p>Important conversations may get special visual treatment, and might be able to bypass DND. + * + * <p>This is only valid for channels that represent conversations, that is, those with a valid + * {@link #getConversationId() conversation id}. + */ + public boolean isImportantConversation() { + return mImportantConvo; + } + + /** * Returns the notification sound for this channel. */ public Uri getSound() { @@ -852,6 +875,7 @@ public final class NotificationChannel implements Parcelable { setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL), parser.getAttributeValue(null, ATT_CONVERSATION_ID)); setDemoted(safeBool(parser, ATT_DEMOTE, false)); + setImportantConversation(safeBool(parser, ATT_IMP_CONVERSATION, false)); } @Nullable @@ -985,6 +1009,9 @@ public final class NotificationChannel implements Parcelable { if (isDemoted()) { out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted())); } + if (isImportantConversation()) { + out.attribute(null, ATT_IMP_CONVERSATION, Boolean.toString(isImportantConversation())); + } // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of // truth and so aren't written to this xml file @@ -1145,7 +1172,8 @@ public final class NotificationChannel implements Parcelable { && mOriginalImportance == that.mOriginalImportance && Objects.equals(getParentChannelId(), that.getParentChannelId()) && Objects.equals(getConversationId(), that.getConversationId()) - && isDemoted() == that.isDemoted(); + && isDemoted() == that.isDemoted() + && isImportantConversation() == that.isImportantConversation(); } @Override @@ -1156,7 +1184,7 @@ public final class NotificationChannel implements Parcelable { isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, - mParentId, mConversationId, mDemoted); + mParentId, mConversationId, mDemoted, mImportantConvo); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1204,7 +1232,8 @@ public final class NotificationChannel implements Parcelable { + ", mOriginalImp=" + mOriginalImportance + ", mParent=" + mParentId + ", mConversationId=" + mConversationId - + ", mDemoted=" + mDemoted; + + ", mDemoted=" + mDemoted + + ", mImportantConvo=" + mImportantConvo; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 1a8e15c0a23e..528b508cecb1 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -46,6 +46,7 @@ import android.service.notification.Adjustment; import android.service.notification.Condition; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -1555,19 +1556,24 @@ public class NotificationManager { public static final int PRIORITY_CATEGORY_MEDIA = 1 << 6; /**System (catch-all for non-never suppressible sounds) are prioritized */ public static final int PRIORITY_CATEGORY_SYSTEM = 1 << 7; + /** + * Conversations are allowed through DND. + */ + public static final int PRIORITY_CATEGORY_CONVERSATIONS = 1 << 8; /** * @hide */ public static final int[] ALL_PRIORITY_CATEGORIES = { - PRIORITY_CATEGORY_ALARMS, - PRIORITY_CATEGORY_MEDIA, - PRIORITY_CATEGORY_SYSTEM, - PRIORITY_CATEGORY_REMINDERS, - PRIORITY_CATEGORY_EVENTS, - PRIORITY_CATEGORY_MESSAGES, - PRIORITY_CATEGORY_CALLS, - PRIORITY_CATEGORY_REPEAT_CALLERS, + PRIORITY_CATEGORY_ALARMS, + PRIORITY_CATEGORY_MEDIA, + PRIORITY_CATEGORY_SYSTEM, + PRIORITY_CATEGORY_REMINDERS, + PRIORITY_CATEGORY_EVENTS, + PRIORITY_CATEGORY_MESSAGES, + PRIORITY_CATEGORY_CALLS, + PRIORITY_CATEGORY_REPEAT_CALLERS, + PRIORITY_CATEGORY_CONVERSATIONS, }; /** Any sender is prioritized. */ @@ -1577,6 +1583,31 @@ public class NotificationManager { /** Only starred contacts are prioritized. */ public static final int PRIORITY_SENDERS_STARRED = 2; + + /** @hide */ + @IntDef(prefix = { "CONVERSATION_SENDERS_" }, value = { + CONVERSATION_SENDERS_ANYONE, + CONVERSATION_SENDERS_IMPORTANT, + CONVERSATION_SENDERS_NONE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConversationSenders {} + /** + * Used to indicate all conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_ANYONE = ZenPolicy.CONVERSATION_SENDERS_ANYONE; + + /** + * Used to indicate important conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_IMPORTANT = + ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; + + /** + * Used to indicate no conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_NONE = ZenPolicy.CONVERSATION_SENDERS_NONE; + /** Notification categories to prioritize. Bitmask of PRIORITY_CATEGORY_* constants. */ public final int priorityCategories; @@ -1589,6 +1620,18 @@ public class NotificationManager { public final int priorityMessageSenders; /** + * Notification senders to prioritize for conversations. One of: + * {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT}, + * {@link #CONVERSATION_SENDERS_ANYONE}. + */ + public final int priorityConversationSenders; + + /** + * @hide + */ + public static final int CONVERSATION_SENDERS_UNSET = -1; + + /** * @hide */ public static final int SUPPRESSED_EFFECTS_UNSET = -1; @@ -1665,21 +1708,6 @@ public class NotificationManager { SUPPRESSED_EFFECT_NOTIFICATION_LIST }; - private static final int[] SCREEN_OFF_SUPPRESSED_EFFECTS = { - SUPPRESSED_EFFECT_SCREEN_OFF, - SUPPRESSED_EFFECT_FULL_SCREEN_INTENT, - SUPPRESSED_EFFECT_LIGHTS, - SUPPRESSED_EFFECT_AMBIENT, - }; - - private static final int[] SCREEN_ON_SUPPRESSED_EFFECTS = { - SUPPRESSED_EFFECT_SCREEN_ON, - SUPPRESSED_EFFECT_PEEK, - SUPPRESSED_EFFECT_STATUS_BAR, - SUPPRESSED_EFFECT_BADGE, - SUPPRESSED_EFFECT_NOTIFICATION_LIST - }; - /** * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode. * Bitmask of SUPPRESSED_EFFECT_* constants. @@ -1718,7 +1746,41 @@ public class NotificationManager { */ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) { this(priorityCategories, priorityCallSenders, priorityMessageSenders, - SUPPRESSED_EFFECTS_UNSET, STATE_UNSET); + SUPPRESSED_EFFECTS_UNSET, STATE_UNSET, CONVERSATION_SENDERS_UNSET); + } + + /** + * Constructs a policy for Do Not Disturb priority mode behavior. + * + * <p> + * Apps that target API levels below {@link Build.VERSION_CODES#R} cannot + * change user-designated values to allow or disallow + * {@link Policy#PRIORITY_CATEGORY_CONVERSATIONS}, from bypassing dnd. + * <p> + * Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can + * only modify the {@link #SUPPRESSED_EFFECT_SCREEN_ON} and + * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} bits of the suppressed visual effects field. + * All other suppressed effects will be ignored and reconstituted from the screen on + * and screen off values. + * <p> + * Apps that target {@link Build.VERSION_CODES#P} or above can set any + * suppressed visual effects. However, if any suppressed effects > + * {@link #SUPPRESSED_EFFECT_SCREEN_ON} are set, {@link #SUPPRESSED_EFFECT_SCREEN_ON} + * and {@link #SUPPRESSED_EFFECT_SCREEN_OFF} will be ignored and reconstituted from + * the more specific suppressed visual effect bits. Apps should migrate to targeting + * specific effects instead of the deprecated {@link #SUPPRESSED_EFFECT_SCREEN_ON} and + * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} effects. + * + * @param priorityCategories bitmask of categories of notifications that can bypass DND. + * @param priorityCallSenders which callers can bypass DND. + * @param priorityMessageSenders which message senders can bypass DND. + * @param suppressedVisualEffects which visual interruptions should be suppressed from + * notifications that are filtered by DND. + */ + public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders, + int suppressedVisualEffects) { + this(priorityCategories, priorityCallSenders, priorityMessageSenders, + suppressedVisualEffects, STATE_UNSET, CONVERSATION_SENDERS_UNSET); } /** @@ -1727,7 +1789,14 @@ public class NotificationManager { * <p> * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot * change user-designated values to allow or disallow - * {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and + * {@link Policy#PRIORITY_CATEGORY_CONVERSATIONS} from bypassing dnd. If you do need + * to change them, use a {@link ZenPolicy} associated with an {@link AutomaticZenRule} + * instead of changing the global setting. + * <p> + * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot + * change user-designated values to allow or disallow + * {@link Policy#PRIORITY_CATEGORY_ALARMS}, + * {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd. * <p> * Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can @@ -1751,28 +1820,27 @@ public class NotificationManager { * notifications that are filtered by DND. */ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders, - int suppressedVisualEffects) { - this.priorityCategories = priorityCategories; - this.priorityCallSenders = priorityCallSenders; - this.priorityMessageSenders = priorityMessageSenders; - this.suppressedVisualEffects = suppressedVisualEffects; - this.state = STATE_UNSET; + int suppressedVisualEffects, int priorityConversationSenders) { + this(priorityCategories, priorityCallSenders, priorityMessageSenders, + suppressedVisualEffects, STATE_UNSET, priorityConversationSenders); } /** @hide */ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders, - int suppressedVisualEffects, int state) { + int suppressedVisualEffects, int state, int priorityConversationSenders) { this.priorityCategories = priorityCategories; this.priorityCallSenders = priorityCallSenders; this.priorityMessageSenders = priorityMessageSenders; this.suppressedVisualEffects = suppressedVisualEffects; this.state = state; + this.priorityConversationSenders = priorityConversationSenders; } + /** @hide */ public Policy(Parcel source) { this(source.readInt(), source.readInt(), source.readInt(), source.readInt(), - source.readInt()); + source.readInt(), source.readInt()); } @Override @@ -1782,6 +1850,7 @@ public class NotificationManager { dest.writeInt(priorityMessageSenders); dest.writeInt(suppressedVisualEffects); dest.writeInt(state); + dest.writeInt(priorityConversationSenders); } @Override @@ -1792,7 +1861,7 @@ public class NotificationManager { @Override public int hashCode() { return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders, - suppressedVisualEffects, state); + suppressedVisualEffects, state, priorityConversationSenders); } @Override @@ -1805,7 +1874,8 @@ public class NotificationManager { && other.priorityMessageSenders == priorityMessageSenders && suppressedVisualEffectsEqual(suppressedVisualEffects, other.suppressedVisualEffects) - && other.state == this.state; + && other.state == this.state + && other.priorityConversationSenders == this.priorityConversationSenders; } private boolean suppressedVisualEffectsEqual(int suppressedEffects, @@ -1867,6 +1937,8 @@ public class NotificationManager { + "priorityCategories=" + priorityCategoriesToString(priorityCategories) + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders) + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders) + + ",priorityConvSenders=" + + conversationSendersToString(priorityConversationSenders) + ",suppressedVisualEffects=" + suppressedEffectsToString(suppressedVisualEffects) + ",areChannelsBypassingDnd=" + (((state & STATE_CHANNELS_BYPASSING_DND) != 0) @@ -2003,6 +2075,7 @@ public class NotificationManager { case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS"; case PRIORITY_CATEGORY_MEDIA: return "PRIORITY_CATEGORY_MEDIA"; case PRIORITY_CATEGORY_SYSTEM: return "PRIORITY_CATEGORY_SYSTEM"; + case PRIORITY_CATEGORY_CONVERSATIONS: return "PRIORITY_CATEGORY_CONVERSATIONS"; default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory; } } @@ -2016,7 +2089,25 @@ public class NotificationManager { } } - public static final @android.annotation.NonNull Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() { + /** + * @hide + */ + public static @NonNull String conversationSendersToString(int priorityConversationSenders) { + switch (priorityConversationSenders) { + case CONVERSATION_SENDERS_ANYONE: + return "anyone"; + case CONVERSATION_SENDERS_IMPORTANT: + return "important"; + case CONVERSATION_SENDERS_NONE: + return "none"; + case CONVERSATION_SENDERS_UNSET: + return "unset"; + } + return "invalidConversationType{" + priorityConversationSenders + "}"; + } + + public static final @android.annotation.NonNull Parcelable.Creator<Policy> CREATOR + = new Parcelable.Creator<Policy>() { @Override public Policy createFromParcel(Parcel in) { return new Policy(in); @@ -2054,6 +2145,11 @@ public class NotificationManager { } /** @hide **/ + public boolean allowConversations() { + return (priorityCategories & PRIORITY_CATEGORY_CONVERSATIONS) != 0; + } + + /** @hide **/ public boolean allowMessages() { return (priorityCategories & PRIORITY_CATEGORY_MESSAGES) != 0; } @@ -2079,6 +2175,11 @@ public class NotificationManager { } /** @hide **/ + public int allowConversationsFrom() { + return priorityConversationSenders; + } + + /** @hide **/ public boolean showFullScreenIntents() { return (suppressedVisualEffects & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 65a5f6b19cd2..50b9d6b47e02 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -163,7 +163,7 @@ import android.os.Vibrator; import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; -import android.os.incremental.IIncrementalManagerNative; +import android.os.incremental.IIncrementalService; import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.permission.PermissionControllerManager; @@ -1299,7 +1299,7 @@ public final class SystemServiceRegistry { return null; } return new IncrementalManager( - IIncrementalManagerNative.Stub.asInterface(b)); + IIncrementalService.Stub.asInterface(b)); }}); registerService(Context.FILE_INTEGRITY_SERVICE, FileIntegrityManager.class, diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 35cf68737ccc..a9a06dabc049 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -27,7 +27,10 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -828,20 +831,39 @@ public final class UiAutomation { } /** - * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE} - * format. + * Takes a screenshot. * * @return The screenshot bitmap on success, null otherwise. */ public Bitmap takeScreenshot() { - final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); - connectionId = mConnectionId; } - // Calling out without a lock held. - return AccessibilityInteractionClient.getInstance() - .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY); + Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(Display.DEFAULT_DISPLAY); + Point displaySize = new Point(); + display.getRealSize(displaySize); + + int rotation = display.getRotation(); + + // Take the screenshot + Bitmap screenShot = null; + try { + // Calling out without a lock held. + screenShot = mUiAutomationConnection.takeScreenshot( + new Rect(0, 0, displaySize.x, displaySize.y), rotation); + if (screenShot == null) { + return null; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while taking screnshot!", re); + return null; + } + + // Optimization + screenShot.setHasAlpha(false); + + return screenShot; } /** diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 4fb974305e48..82e988109db8 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -21,6 +21,8 @@ import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Binder; import android.os.IBinder; @@ -178,6 +180,23 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override + public Bitmap takeScreenshot(Rect crop, int rotation) { + synchronized (mLock) { + throwIfCalledByNotTrustedUidLocked(); + throwIfShutdownLocked(); + throwIfNotConnectedLocked(); + } + final long identity = Binder.clearCallingIdentity(); + try { + int width = crop.width(); + int height = crop.height(); + return SurfaceControl.screenshot(crop, width, height, rotation); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public boolean clearWindowContentFrameStats(int windowId) throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); @@ -430,8 +449,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY - | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS - | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT); + | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java index c2a76c5014c0..2ac5ebfc7c84 100644 --- a/core/java/android/app/admin/DevicePolicyKeyguardService.java +++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java @@ -22,7 +22,7 @@ import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; /** * Client interface for providing the SystemUI with secondary lockscreen information. @@ -43,14 +43,14 @@ public class DevicePolicyKeyguardService extends Service { @Override public void onSurfaceReady(@Nullable IBinder hostInputToken, IKeyguardCallback callback) { mCallback = callback; - SurfaceControl surfaceControl = + SurfaceControlViewHost.SurfacePackage surfacePackage = DevicePolicyKeyguardService.this.onSurfaceReady(hostInputToken); if (mCallback != null) { try { - mCallback.onSurfaceControlCreated(surfaceControl); + mCallback.onRemoteContentReady(surfacePackage); } catch (RemoteException e) { - Log.e(TAG, "Failed to return created SurfaceControl", e); + Log.e(TAG, "Failed to return created SurfacePackage", e); } } } @@ -65,11 +65,11 @@ public class DevicePolicyKeyguardService extends Service { /** * Called by keyguard once the host surface for the secondary lockscreen is ready to display * remote content. - * @return the {@link SurfaceControl} for the Surface the secondary lockscreen content is - * attached to. + * @return the {@link SurfaceControlViewHost.SurfacePackage} for the Surface the + * secondary lockscreen content is attached to. */ @Nullable - public SurfaceControl onSurfaceReady(@Nullable IBinder hostInputToken) { + public SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable IBinder hostInputToken) { return null; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3676a9b2548f..759979100483 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2090,6 +2090,13 @@ public class DevicePolicyManager { public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5; /** + * Enable blocking of non-whitelisted activities from being started into a locked task. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK = 1 << 6; + + /** * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}. * * @hide @@ -2102,7 +2109,8 @@ public class DevicePolicyManager { LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_OVERVIEW, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, - LOCK_TASK_FEATURE_KEYGUARD + LOCK_TASK_FEATURE_KEYGUARD, + LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK }) public @interface LockTaskFeature {} @@ -3722,6 +3730,11 @@ public class DevicePolicyManager { * requires that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}. * <p> + * When this policy is set by a device owner, profile owner of an organization-owned device or + * an admin on the primary user, the device will be factory reset after too many incorrect + * password attempts. When set by a profile owner or an admin on a secondary user or a managed + * profile, only the corresponding user or profile will be wiped. + * <p> * To implement any other policy (e.g. wiping data for a particular application only, erasing or * revoking credentials, or reporting the failure to a server), you should implement * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)} instead. Do not @@ -3790,10 +3803,12 @@ public class DevicePolicyManager { } /** - * Returns the profile with the smallest maximum failed passwords for wipe, - * for the given user. So for primary user, it might return the primary or - * a managed profile. For a secondary user, it would be the same as the - * user passed in. + * Returns the user that will be wiped first when too many failed attempts are made to unlock + * user {@code userHandle}. That user is either the same as {@code userHandle} or belongs to the + * same profile group. When there is no such policy, returns {@code UserHandle.USER_NULL}. + * E.g. managed profile user may be wiped as a result of failed primary profile password + * attempts when using unified challenge. Primary user may be wiped as a result of failed + * password attempts on the managed profile on an organization-owned device. * @hide Used only by Keyguard */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) diff --git a/core/java/android/app/admin/IKeyguardCallback.aidl b/core/java/android/app/admin/IKeyguardCallback.aidl index 81e7d4dee902..856033dc8129 100644 --- a/core/java/android/app/admin/IKeyguardCallback.aidl +++ b/core/java/android/app/admin/IKeyguardCallback.aidl @@ -15,13 +15,13 @@ */ package android.app.admin; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; /** * Internal IPC interface for informing the keyguard of events on the secondary lockscreen. * @hide */ interface IKeyguardCallback { - oneway void onSurfaceControlCreated(in SurfaceControl remoteSurfaceControl); + oneway void onRemoteContentReady(in SurfaceControlViewHost.SurfacePackage surfacePackage); oneway void onDismiss(); } diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java new file mode 100644 index 000000000000..5b367894ce69 --- /dev/null +++ b/core/java/android/app/compat/CompatChanges.java @@ -0,0 +1,108 @@ +/* + * 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.app.compat; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; + +import com.android.internal.compat.IPlatformCompat; + +/** + * CompatChanges APIs - to be used by platform code only (including mainline + * modules). + * + * @hide + */ +@SystemApi +public final class CompatChanges { + private CompatChanges() {} + + /** + * Query if a given compatibility change is enabled for the current process. This method is + * intended to be called by code running inside a process of the affected app only. + * + * <p>If this method returns {@code true}, the calling code should implement the compatibility + * change, resulting in differing behaviour compared to earlier releases. If this method returns + * {@code false}, the calling code should behave as it did in earlier releases. + * + * @param changeId The ID of the compatibility change in question. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId) { + return Compatibility.isChangeEnabled(changeId); + } + + /** + * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an + * app from a different process that's performing work for the app. + * + * <p> Note that this involves a binder call to the system server (unless running in the system + * server). If the binder call fails, a {@code RuntimeException} will be thrown. + * + * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it + * doesn't, a {@code RuntimeException} will be thrown. + * + * @param changeId The ID of the compatibility change in question. + * @param packageName The package name of the app in question. + * @param user The user that the operation is done for. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId, @NonNull String packageName, + @NonNull UserHandle user) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + try { + return platformCompat.isChangeEnabledByPackageName(changeId, packageName, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an + * app from a different process that's performing work for the app. + * + * <p> Note that this involves a binder call to the system server (unless running in the system + * server). If the binder call fails, {@code RuntimeException} will be thrown. + * + * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it + * doesn't, a {@code RuntimeException} will be thrown. + * + * <p> Returns {@code true} if there are no installed packages for the required UID, or if the + * change is enabled for ALL of the installed packages associated with the provided UID. Please + * use a more specific API if you want a different behaviour for multi-package UIDs. + * + * @param changeId The ID of the compatibility change in question. + * @param uid The UID of the app in question. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId, int uid) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + try { + return platformCompat.isChangeEnabledByUid(changeId, uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index de8f4700de2d..5ead0c90c80e 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -18,7 +18,7 @@ package android.app.timedetector; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; /** * System private API to communicate with time detector service. @@ -34,7 +34,7 @@ import android.app.timedetector.PhoneTimeSuggestion; * {@hide} */ interface ITimeDetectorService { - void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion); void suggestManualTime(in ManualTimeSuggestion timeSuggestion); void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion); + void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion); } diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl index f5e240549a9a..d9b038692c62 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl @@ -16,4 +16,4 @@ package android.app.timedetector; -parcelable PhoneTimeSuggestion; +parcelable TelephonyTimeSuggestion; diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java index 16288e82d452..c0e8957727cf 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java @@ -18,7 +18,6 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.TimestampedValue; @@ -51,19 +50,17 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public final class PhoneTimeSuggestion implements Parcelable { +public final class TelephonyTimeSuggestion implements Parcelable { /** @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR = - new Parcelable.Creator<PhoneTimeSuggestion>() { - public PhoneTimeSuggestion createFromParcel(Parcel in) { - return PhoneTimeSuggestion.createFromParcel(in); + public static final @NonNull Parcelable.Creator<TelephonyTimeSuggestion> CREATOR = + new Parcelable.Creator<TelephonyTimeSuggestion>() { + public TelephonyTimeSuggestion createFromParcel(Parcel in) { + return TelephonyTimeSuggestion.createFromParcel(in); } - public PhoneTimeSuggestion[] newArray(int size) { - return new PhoneTimeSuggestion[size]; + public TelephonyTimeSuggestion[] newArray(int size) { + return new TelephonyTimeSuggestion[size]; } }; @@ -71,15 +68,15 @@ public final class PhoneTimeSuggestion implements Parcelable { @Nullable private final TimestampedValue<Long> mUtcTime; @Nullable private ArrayList<String> mDebugInfo; - private PhoneTimeSuggestion(Builder builder) { + private TelephonyTimeSuggestion(Builder builder) { mSlotIndex = builder.mSlotIndex; mUtcTime = builder.mUtcTime; mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null; } - private static PhoneTimeSuggestion createFromParcel(Parcel in) { + private static TelephonyTimeSuggestion createFromParcel(Parcel in) { int slotIndex = in.readInt(); - PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(slotIndex) + TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(in.readParcelable(null /* classLoader */)) .build(); @SuppressWarnings("unchecked") @@ -105,7 +102,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns an identifier for the source of this suggestion. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. */ public int getSlotIndex() { return mSlotIndex; @@ -114,7 +111,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the suggested time or {@code null} if there isn't one. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}. */ @Nullable public TimestampedValue<Long> getUtcTime() { @@ -124,7 +121,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns debug metadata for the suggestion. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ @NonNull public List<String> getDebugInfo() { @@ -135,7 +132,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { @@ -147,7 +144,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull List<String> debugInfo) { if (mDebugInfo == null) { @@ -164,7 +161,7 @@ public final class PhoneTimeSuggestion implements Parcelable { if (o == null || getClass() != o.getClass()) { return false; } - PhoneTimeSuggestion that = (PhoneTimeSuggestion) o; + TelephonyTimeSuggestion that = (TelephonyTimeSuggestion) o; return mSlotIndex == that.mSlotIndex && Objects.equals(mUtcTime, that.mUtcTime); } @@ -176,7 +173,7 @@ public final class PhoneTimeSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeSuggestion{" + return "TelephonyTimeSuggestion{" + "mSlotIndex='" + mSlotIndex + '\'' + ", mUtcTime=" + mUtcTime + ", mDebugInfo=" + mDebugInfo @@ -184,11 +181,10 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeSuggestion} instances. + * Builds {@link TelephonyTimeSuggestion} instances. * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class Builder { private final int mSlotIndex; @Nullable private TimestampedValue<Long> mUtcTime; @@ -197,7 +193,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Creates a builder with the specified {@code slotIndex}. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. */ public Builder(int slotIndex) { mSlotIndex = slotIndex; @@ -206,7 +202,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}. */ @NonNull public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) { @@ -222,7 +218,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { @@ -233,10 +229,10 @@ public final class PhoneTimeSuggestion implements Parcelable { return this; } - /** Returns the {@link PhoneTimeSuggestion}. */ + /** Returns the {@link TelephonyTimeSuggestion}. */ @NonNull - public PhoneTimeSuggestion build() { - return new PhoneTimeSuggestion(this); + public TelephonyTimeSuggestion build() { + return new TelephonyTimeSuggestion(this); } } } diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 2412fb3994ed..84ad495da09b 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -18,7 +18,6 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; -import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.SystemClock; @@ -29,7 +28,6 @@ import android.os.TimestampedValue; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_DETECTOR_SERVICE) public interface TimeDetector { @@ -47,12 +45,12 @@ public interface TimeDetector { } /** - * Suggests the current phone-signal derived time to the detector. The detector may ignore the - * signal if better signals are available such as those that come from more reliable sources or - * were determined more recently. + * Suggests a telephony-signal derived time to the detector. The detector may ignore the signal + * if better signals are available such as those that come from more reliable sources or were + * determined more recently. */ - @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion); + @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE) + void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion); /** * Suggests the user's manually entered current time to the detector. diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java index 1683817740c3..c1d66672f9d2 100644 --- a/core/java/android/app/timedetector/TimeDetectorImpl.java +++ b/core/java/android/app/timedetector/TimeDetectorImpl.java @@ -40,12 +40,12 @@ public final class TimeDetectorImpl implements TimeDetector { } @Override - public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) { if (DEBUG) { - Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion); + Log.d(TAG, "suggestTelephonyTime called: " + timeSuggestion); } try { - mITimeDetectorService.suggestPhoneTime(timeSuggestion); + mITimeDetectorService.suggestTelephonyTime(timeSuggestion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl index df6438319ab7..b06f4b8ecdb8 100644 --- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl +++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl @@ -17,7 +17,7 @@ package android.app.timezonedetector; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; /** * System private API to communicate with time zone detector service. @@ -34,5 +34,5 @@ import android.app.timezonedetector.PhoneTimeZoneSuggestion; */ interface ITimeZoneDetectorService { void suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion); - void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion); + void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion); } diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl index 3ad903bb5949..b57ad20734be 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl @@ -16,4 +16,4 @@ package android.app.timezonedetector; -parcelable PhoneTimeZoneSuggestion; +parcelable TelephonyTimeZoneSuggestion; diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java index 0544ccd3f4c5..150c01d59899 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java @@ -19,7 +19,6 @@ package android.app.timezonedetector; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -57,20 +56,18 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public final class PhoneTimeZoneSuggestion implements Parcelable { +public final class TelephonyTimeZoneSuggestion implements Parcelable { /** @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull - public static final Creator<PhoneTimeZoneSuggestion> CREATOR = - new Creator<PhoneTimeZoneSuggestion>() { - public PhoneTimeZoneSuggestion createFromParcel(Parcel in) { - return PhoneTimeZoneSuggestion.createFromParcel(in); + public static final Creator<TelephonyTimeZoneSuggestion> CREATOR = + new Creator<TelephonyTimeZoneSuggestion>() { + public TelephonyTimeZoneSuggestion createFromParcel(Parcel in) { + return TelephonyTimeZoneSuggestion.createFromParcel(in); } - public PhoneTimeZoneSuggestion[] newArray(int size) { - return new PhoneTimeZoneSuggestion[size]; + public TelephonyTimeZoneSuggestion[] newArray(int size) { + return new TelephonyTimeZoneSuggestion[size]; } }; @@ -79,7 +76,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * the same {@code slotIndex}. */ @NonNull - public static PhoneTimeZoneSuggestion createEmptySuggestion( + public static TelephonyTimeZoneSuggestion createEmptySuggestion( int slotIndex, @NonNull String debugInfo) { return new Builder(slotIndex).addDebugInfo(debugInfo).build(); } @@ -147,7 +144,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Quality private final int mQuality; @Nullable private List<String> mDebugInfo; - private PhoneTimeZoneSuggestion(Builder builder) { + private TelephonyTimeZoneSuggestion(Builder builder) { mSlotIndex = builder.mSlotIndex; mZoneId = builder.mZoneId; mMatchType = builder.mMatchType; @@ -156,15 +153,16 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } @SuppressWarnings("unchecked") - private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) { + private static TelephonyTimeZoneSuggestion createFromParcel(Parcel in) { // Use the Builder so we get validation during build(). int slotIndex = in.readInt(); - PhoneTimeZoneSuggestion suggestion = new Builder(slotIndex) + TelephonyTimeZoneSuggestion suggestion = new Builder(slotIndex) .setZoneId(in.readString()) .setMatchType(in.readInt()) .setQuality(in.readInt()) .build(); - List<String> debugInfo = in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader()); + List<String> debugInfo = + in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader()); if (debugInfo != null) { suggestion.addDebugInfo(debugInfo); } @@ -188,7 +186,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns an identifier for the source of this suggestion. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}. */ public int getSlotIndex() { return mSlotIndex; @@ -198,7 +196,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that * the caller is no longer sure what the current time zone is. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}. */ @Nullable public String getZoneId() { @@ -209,7 +207,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * Returns information about how the suggestion was determined which could be used to rank * suggestions when several are available from different sources. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}. */ @MatchType public int getMatchType() { @@ -219,7 +217,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns information about the likelihood of the suggested zone being correct. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}. */ @Quality public int getQuality() { @@ -229,7 +227,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns debug metadata for the suggestion. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ @NonNull public List<String> getDebugInfo() { @@ -240,7 +238,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { @@ -252,7 +250,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull List<String> debugInfo) { if (mDebugInfo == null) { @@ -269,7 +267,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { if (o == null || getClass() != o.getClass()) { return false; } - PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o; + TelephonyTimeZoneSuggestion that = (TelephonyTimeZoneSuggestion) o; return mSlotIndex == that.mSlotIndex && mMatchType == that.mMatchType && mQuality == that.mQuality @@ -283,7 +281,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeZoneSuggestion{" + return "TelephonyTimeZoneSuggestion{" + "mSlotIndex=" + mSlotIndex + ", mZoneId='" + mZoneId + '\'' + ", mMatchType=" + mMatchType @@ -293,11 +291,10 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeZoneSuggestion} instances. + * Builds {@link TelephonyTimeZoneSuggestion} instances. * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class Builder { private final int mSlotIndex; @Nullable private String mZoneId; @@ -308,7 +305,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Creates a builder with the specified {@code slotIndex}. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}. */ public Builder(int slotIndex) { mSlotIndex = slotIndex; @@ -317,7 +314,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}. */ @NonNull public Builder setZoneId(@Nullable String zoneId) { @@ -328,7 +325,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}. */ @NonNull public Builder setMatchType(@MatchType int matchType) { @@ -339,7 +336,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}. */ @NonNull public Builder setQuality(@Quality int quality) { @@ -350,7 +347,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { @@ -388,11 +385,11 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } } - /** Returns the {@link PhoneTimeZoneSuggestion}. */ + /** Returns the {@link TelephonyTimeZoneSuggestion}. */ @NonNull - public PhoneTimeZoneSuggestion build() { + public TelephonyTimeZoneSuggestion build() { validate(); - return new PhoneTimeZoneSuggestion(this); + return new TelephonyTimeZoneSuggestion(this); } } } diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index b4f608787d4a..20761ad2d447 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -18,7 +18,6 @@ package android.app.timezonedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; -import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; @@ -27,7 +26,6 @@ import android.content.Context; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) public interface TimeZoneDetector { @@ -49,9 +47,8 @@ public interface TimeZoneDetector { * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion); + @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE) + void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion); /** * Suggests the current time zone, determined for the user's manually information, to the diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java index 27b8374db172..0ada88500193 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java @@ -40,12 +40,12 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector { } @Override - public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { + public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { - Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion); + Log.d(TAG, "suggestTelephonyTimeZone called: " + timeZoneSuggestion); } try { - mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); + mITimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2e93d43f7c15..01ccb86fb129 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1861,15 +1861,19 @@ public final class BluetoothAdapter { } /** - * Connects all enabled and supported bluetooth profiles between the local and remote device + * Connects all enabled and supported bluetooth profiles between the local and remote device. + * Connection is asynchronous and you should listen to each profile's broadcast intent + * ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. For example, + * to verify a2dp is connected, you would listen for + * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} * * @param device is the remote device with which to connect these profiles - * @return true if all profiles successfully connected, false if an error occurred + * @return true if message sent to try to connect all profiles, false if an error occurred * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); @@ -1886,15 +1890,19 @@ public final class BluetoothAdapter { } /** - * Disconnects all enabled and supported bluetooth profiles between the local and remote device + * Disconnects all enabled and supported bluetooth profiles between the local and remote device. + * Disconnection is asynchronous and you should listen to each profile's broadcast intent + * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example, + * to verify a2dp is disconnected, you would listen for + * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} * * @param device is the remote device with which to disconnect these profiles - * @return true if all profiles successfully disconnected, false if an error occurred + * @return true if message sent to try to disconnect all profiles, false if an error occurred * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index 439d53661b8e..d25f413bb65f 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -332,9 +332,12 @@ public abstract class AtomicFormula extends IntegrityFormula { * Constructs a new {@link StringAtomicFormula} together with handling the necessary * hashing for the given key. * - * <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 33 characters. + * <p> The value will be automatically hashed with SHA256 and the hex digest will be + * computed when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 + * characters. + * + * <p> The APP_CERTIFICATES and INSTALLER_CERTIFICATES are always delivered in hashed + * form. So the isHashedValue is set to true by default. * * @throws IllegalArgumentException if {@code key} cannot be used with string value. */ @@ -348,7 +351,10 @@ public abstract class AtomicFormula extends IntegrityFormula { String.format( "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); - mIsHashedValue = !mValue.equals(value); + mIsHashedValue = + key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE + ? true + : !mValue.equals(value); } StringAtomicFormula(Parcel in) { diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index de153d00b48b..edc20d9f65ad 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -323,6 +323,7 @@ public class CrossProfileApps { */ @RequiresPermission( allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES, + android.Manifest.permission.UPDATE_APP_OPS_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) { try { @@ -363,6 +364,7 @@ public class CrossProfileApps { */ @RequiresPermission( allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES, + android.Manifest.permission.UPDATE_APP_OPS_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void resetInteractAcrossProfilesAppOps( @NonNull Collection<String> previousCrossProfilePackages, diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 50bb3c721763..04923590b413 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -20,11 +20,13 @@ import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.LauncherApps; import android.content.pm.IPackageInstallerCallback; +import android.content.pm.IShortcutChangeCallback; import android.content.pm.PackageInstaller; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; @@ -66,7 +68,8 @@ interface ILauncherApps { in UserHandle user); ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, - in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user); + in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName, + 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 id, @@ -89,4 +92,10 @@ interface ILauncherApps { void registerPackageInstallerCallback(String callingPackage, in IPackageInstallerCallback callback); ParceledListSlice getAllSessions(String callingPackage); + + void registerShortcutChangeCallback(String callingPackage, long changedSince, + String packageName, in List shortcutIds, in List<LocusId> locusIds, + in ComponentName componentName, int flags, in IShortcutChangeCallback callback, + int callbackId); + void unregisterShortcutChangeCallback(String callingPackage, int callbackId); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 93126b8002ad..6552d1b5a824 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -679,9 +679,9 @@ interface IPackageManager { boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags); - String getSystemTextClassifierPackageName(); + String getDefaultTextClassifierPackageName(); - String[] getSystemTextClassifierPackages(); + String getSystemTextClassifierPackageName(); String getAttentionServicePackageName(); diff --git a/core/java/android/content/pm/IShortcutChangeCallback.aidl b/core/java/android/content/pm/IShortcutChangeCallback.aidl new file mode 100644 index 000000000000..fed4e4a796f9 --- /dev/null +++ b/core/java/android/content/pm/IShortcutChangeCallback.aidl @@ -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 android.content.pm; + +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; +import android.os.UserHandle; + +import java.util.List; + +/** + * Interface for LauncherApps#ShortcutChangeCallbackProxy. + * + * @hide + */ +oneway interface IShortcutChangeCallback +{ + void onShortcutsAddedOrUpdated(String packageName, in List<ShortcutInfo> shortcuts, + in UserHandle user); + + void onShortcutsRemoved(String packageName, in List<ShortcutInfo> shortcuts, + in UserHandle user); +}
\ No newline at end of file diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 747e929eda98..9e85fc301a0c 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -29,10 +29,6 @@ interface IShortcutService { boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, int userId); - ParceledListSlice getDynamicShortcuts(String packageName, int userId); - - ParceledListSlice getManifestShortcuts(String packageName, int userId); - boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, int userId); @@ -40,8 +36,6 @@ interface IShortcutService { void removeAllDynamicShortcuts(String packageName, int userId); - ParceledListSlice getPinnedShortcuts(String packageName, int userId); - boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId); boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index cea0b6b5f3ad..73c9e4d843b7 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -34,6 +34,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionCallbackDelegate; import android.content.pm.PackageInstaller.SessionInfo; @@ -61,15 +62,21 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; + +import com.android.internal.util.function.pooled.PooledLambda; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; @@ -152,6 +159,9 @@ public class LauncherApps { private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>(); private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>(); + private final Map<Integer, Pair<Executor, ShortcutChangeCallback>> + mShortcutChangeCallbacks = new HashMap<>(); + /** * Callbacks for package changes to this and related managed profiles. */ @@ -406,6 +416,9 @@ public class LauncherApps { List<String> mShortcutIds; @Nullable + List<LocusId> mLocusIds; + + @Nullable ComponentName mActivity; @QueryFlags @@ -442,6 +455,19 @@ public class LauncherApps { } /** + * If non-null, return only the specified shortcuts by locus ID. When setting this field, + * a package name must also be set with {@link #setPackage}. + * + * @hide + */ + @SystemApi + @NonNull + public ShortcutQuery setLocusIds(@Nullable List<LocusId> locusIds) { + mLocusIds = locusIds; + return this; + } + + /** * If non-null, returns only shortcuts associated with the activity; i.e. * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal * to {@code activity}. @@ -469,6 +495,95 @@ public class LauncherApps { } } + /** + * Callbacks for shortcut changes to this and related managed profiles. + * + * @hide + */ + public interface ShortcutChangeCallback { + /** + * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to + * register this callback, have been added or updated. + * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery) + * + * <p>Only the applications that are allowed to access the shortcut information, + * as defined in {@link #hasShortcutHostPermission()}, will receive it. + * + * @param packageName The name of the package that has the shortcuts. + * @param shortcuts Shortcuts from the package that have updated or added. Only "key" + * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}. + * @param user The UserHandle of the profile that generated the change. + * + * @see ShortcutManager + */ + default void onShortcutsAddedOrUpdated(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {} + + /** + * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to + * register this callback, have been removed. + * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery) + * + * <p>Only the applications that are allowed to access the shortcut information, + * as defined in {@link #hasShortcutHostPermission()}, will receive it. + * + * @param packageName The name of the package that has the shortcuts. + * @param shortcuts Shortcuts from the package that have been removed. Only "key" + * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}. + * @param user The UserHandle of the profile that generated the change. + * + * @see ShortcutManager + */ + default void onShortcutsRemoved(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {} + } + + /** + * Callback proxy class for {@link ShortcutChangeCallback} + * + * @hide + */ + private static class ShortcutChangeCallbackProxy extends + android.content.pm.IShortcutChangeCallback.Stub { + private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences; + + ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) { + mRemoteReferences = new WeakReference<>(remoteReferences); + } + + @Override + public void onShortcutsAddedOrUpdated(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { + Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get(); + if (remoteReferences == null) { + // Binder is dead. + return; + } + + final Executor executor = remoteReferences.first; + final ShortcutChangeCallback callback = remoteReferences.second; + executor.execute( + PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated, + callback, packageName, shortcuts, user).recycleOnUse()); + } + + @Override + public void onShortcutsRemoved(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { + Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get(); + if (remoteReferences == null) { + // Binder is dead. + return; + } + + final Executor executor = remoteReferences.first; + final ShortcutChangeCallback callback = remoteReferences.second; + executor.execute( + PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved, + callback, packageName, shortcuts, user).recycleOnUse()); + } + } + /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; @@ -924,8 +1039,8 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity, - query.mQueryFlags, user) + query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, + query.mActivity, query.mQueryFlags, user) .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1560,6 +1675,63 @@ public class LauncherApps { } /** + * Register a callback to watch for shortcut change events in this user and managed profiles. + * + * @param callback The callback to register. + * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching + * shortcuts will be returned by the callback. + * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main + * thread of your application, you can use {@link android.content.Context#getMainExecutor()}. + * + * @hide + */ + public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback, + @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) { + Objects.requireNonNull(callback, "Callback cannot be null"); + Objects.requireNonNull(query, "Query cannot be null"); + Objects.requireNonNull(executor, "Executor cannot be null"); + + synchronized (mShortcutChangeCallbacks) { + final int callbackId = callback.hashCode(); + final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback); + mShortcutChangeCallbacks.put(callbackId, state); + try { + mService.registerShortcutChangeCallback(mContext.getPackageName(), + query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, + query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state), + callbackId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters a callback that was previously registered. + * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor) + * + * @param callback Callback to be unregistered. + * + * @hide + */ + public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) { + Objects.requireNonNull(callback, "Callback cannot be null"); + + synchronized (mShortcutChangeCallbacks) { + final int callbackId = callback.hashCode(); + if (mShortcutChangeCallbacks.containsKey(callbackId)) { + mShortcutChangeCallbacks.remove(callbackId); + try { + mService.unregisterShortcutChangeCallback(mContext.getPackageName(), + callbackId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** * A helper method to extract a {@link PinItemRequest} set to * the {@link #EXTRA_PIN_ITEM_REQUEST} extra. */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6d5e8fb0240e..5a0bcf04c87f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3354,6 +3354,22 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16; /** + * Permission flag: The permission is whitelisted to not be auto-revoked when app goes unused. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 1 << 17; + + /** + * Permission flag: Whether {@link #FLAG_PERMISSION_DONT_AUTO_REVOKE} state was set by user. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 1 << 18; + + /** * Permission flags: Reserved for use by the permission controller. * * @hide @@ -3404,7 +3420,9 @@ public abstract class PackageManager { | FLAG_PERMISSION_APPLY_RESTRICTION | FLAG_PERMISSION_GRANTED_BY_ROLE | FLAG_PERMISSION_REVOKED_COMPAT - | FLAG_PERMISSION_ONE_TIME; + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_DONT_AUTO_REVOKE + | FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET; /** * Injected activity in app that forwards user to setting activity of that app. @@ -4227,7 +4245,8 @@ public abstract class PackageManager { FLAG_PERMISSION_APPLY_RESTRICTION, FLAG_PERMISSION_GRANTED_BY_ROLE, FLAG_PERMISSION_REVOKED_COMPAT, - FLAG_PERMISSION_ONE_TIME + FLAG_PERMISSION_ONE_TIME, + FLAG_PERMISSION_DONT_AUTO_REVOKE }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionFlags {} @@ -7364,6 +7383,8 @@ public abstract class PackageManager { case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE"; case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT"; case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME"; + case FLAG_PERMISSION_DONT_AUTO_REVOKE: return "DONT_AUTO_REVOKE"; + case FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET: return "DONT_AUTO_REVOKE_USER_SET"; default: return Integer.toString(flag); } } @@ -7602,14 +7623,15 @@ public abstract class PackageManager { } /** - * @return the system defined text classifier package name, or null if there's none. + * @return the default text classifier package name, or null if there's none. * * @hide */ @Nullable - public String getSystemTextClassifierPackageName() { + @TestApi + public String getDefaultTextClassifierPackageName() { throw new UnsupportedOperationException( - "getSystemTextClassifierPackageName not implemented in subclass"); + "getDefaultTextClassifierPackageName not implemented in subclass"); } /** @@ -7617,10 +7639,11 @@ public abstract class PackageManager { * * @hide */ - @NonNull - public String[] getSystemTextClassifierPackages() { + @Nullable + @TestApi + public String getSystemTextClassifierPackageName() { throw new UnsupportedOperationException( - "getSystemTextClassifierPackages not implemented in subclass"); + "getSystemTextClassifierPackageName not implemented in subclass"); } /** diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index e6f682d22b14..a11a1dd5a68b 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -23,6 +23,7 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.LauncherApps.ShortcutQuery; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -45,8 +46,8 @@ public abstract class ShortcutServiceInternal { getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, - @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags, - int userId, int callingPid, int callingUid); + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, + @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid); public abstract boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 0549c34cc034..6f30ecd9b281 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -15,5 +15,15 @@ "name": "FrameworksInstantAppResolverTests", "file_patterns": ["(/|^)InstantApp[^/]*"] } + ], + "postsubmit": [ + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert" + } + ] + } ] } diff --git a/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java new file mode 100644 index 000000000000..5544eaeca7f3 --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java @@ -0,0 +1,79 @@ +/* + * 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.hardware.biometrics; + +import android.os.NativeHandle; +import android.os.ParcelFileDescriptor; + +import java.io.IOException; + +/** + * A class that contains utilities for IBiometricNativeHandle. + * + * @hide + */ +public final class BiometricNativeHandleUtils { + + private BiometricNativeHandleUtils() { + } + + /** + * Converts a {@link NativeHandle} into an {@link IBiometricNativeHandle} by duplicating the + * underlying file descriptors. + * + * Both the original and new handle must be closed after use. + * + * @param h {@link NativeHandle}. Usually used to identify a WindowManager window. Can be null. + * @return A {@link IBiometricNativeHandle} representation of {@code h}. Will be null if + * {@code h} or its raw file descriptors are null. + */ + public static IBiometricNativeHandle dup(NativeHandle h) { + IBiometricNativeHandle handle = null; + if (h != null && h.getFileDescriptors() != null && h.getInts() != null) { + handle = new IBiometricNativeHandle(); + handle.ints = h.getInts().clone(); + handle.fds = new ParcelFileDescriptor[h.getFileDescriptors().length]; + for (int i = 0; i < h.getFileDescriptors().length; ++i) { + try { + handle.fds[i] = ParcelFileDescriptor.dup(h.getFileDescriptors()[i]); + } catch (IOException e) { + return null; + } + } + } + return handle; + } + + /** + * Closes the handle's file descriptors. + * + * @param h {@link IBiometricNativeHandle} handle. + */ + public static void close(IBiometricNativeHandle h) { + if (h != null) { + for (ParcelFileDescriptor fd : h.fds) { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + // do nothing. + } + } + } + } + } +} diff --git a/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl new file mode 100644 index 000000000000..6dcdc1be3a50 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware.biometrics; + +/** + * Representation of a native handle. + * Copied from /common/aidl/android/hardware/common/NativeHandle.aidl + * @hide + */ +parcelable IBiometricNativeHandle { + ParcelFileDescriptor[] fds; + int[] ints; +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 55ebe285af1e..6bda46b0b692 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -29,7 +29,9 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricNativeHandleUtils; import android.hardware.biometrics.CryptoObject; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -38,6 +40,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.RemoteException; import android.os.Trace; @@ -245,6 +248,19 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * Defaults to {@link FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, + * int[], NativeHandle)} with {@code windowId} set to null. + * + * @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[], + * NativeHandle) + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void enroll(int userId, byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { + enroll(userId, token, cancel, callback, disabledFeatures, null /* windowId */); + } + + /** * Request face authentication enrollment. This call operates the face authentication hardware * and starts capturing images. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when @@ -259,11 +275,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @param flags optional flags * @param userId the user to whom this face will belong to * @param callback an object to receive enrollment events + * @param windowId optional ID of a camera preview window for a single-camera device. Must be + * null if not used. * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) public void enroll(int userId, byte[] token, CancellationSignal cancel, - EnrollmentCallback callback, int[] disabledFeatures) { + EnrollmentCallback callback, int[] disabledFeatures, @Nullable NativeHandle windowId) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } @@ -278,20 +296,72 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enroll"); mService.enroll(userId, mToken, token, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures); + mContext.getOpPackageName(), disabledFeatures, handle); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); - if (callback != null) { - // Though this may not be a hardware issue, it will cause apps to give up or - // try again later. - callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, - getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, + // Though this may not be a hardware issue, it will cause apps to give up or + // try again later. + callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, + getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */)); + } finally { + Trace.endSection(); + BiometricNativeHandleUtils.close(handle); + } + } + } + + /** + * Request face authentication enrollment for a remote client, for example Android Auto. + * This call operates the face authentication hardware and starts capturing images. + * Progress will be indicated by callbacks to the + * {@link EnrollmentCallback} object. It terminates when + * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or + * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * + * @param token a unique token provided by a recent creation or verification of device + * credentials (e.g. pin, pattern or password). + * @param cancel an object that can be used to cancel enrollment + * @param userId the user to whom this face will belong to + * @param callback an object to receive enrollment events + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void enrollRemotely(int userId, byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { + if (callback == null) { + throw new IllegalArgumentException("Must supply an enrollment callback"); + } + + if (cancel != null) { + if (cancel.isCanceled()) { + Log.w(TAG, "enrollRemotely is already canceled."); + return; + } else { + cancel.setOnCancelListener(new OnEnrollCancelListener()); + } + } + + if (mService != null) { + try { + mEnrollmentCallback = callback; + Trace.beginSection("FaceManager#enrollRemotely"); + mService.enrollRemotely(userId, mToken, token, mServiceReceiver, + mContext.getOpPackageName(), disabledFeatures); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in enrollRemotely: ", e); + // Though this may not be a hardware issue, it will cause apps to give up or + // try again later. + callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, + getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); - } } finally { Trace.endSection(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 68a4aef7af08..8ba24735c039 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.face; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.IFaceServiceReceiver; @@ -51,6 +52,10 @@ interface IFaceService { // Start face enrollment void enroll(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures, in IBiometricNativeHandle windowId); + + // Start remote face enrollment + void enrollRemotely(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index ff9d14510d4b..f6717c77f90e 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -32,7 +32,9 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricNativeHandleUtils; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -41,6 +43,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; @@ -403,15 +406,33 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * Defaults to {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, + * AuthenticationCallback, Handler, int, NativeHandle)} with {@code windowId} set to null. + * + * @see FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, + * AuthenticationCallback, Handler, int, NativeHandle) + * + * @hide + */ + @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) + public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, + int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { + authenticate(crypto, cancel, flags, callback, handler, userId, null /* windowId */); + } + + /** * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not * display the BiometricPrompt. * @param userId the user ID that the fingerprint hardware will authenticate for. + * @param windowId for optical fingerprint sensors that require active illumination by the OLED + * display. Should be null for devices that don't require illumination. * @hide */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, - int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { + int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId, + @Nullable NativeHandle windowId) { if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } @@ -425,26 +446,44 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - if (mService != null) try { - useHandler(handler); - mAuthenticationCallback = callback; - mCryptoObject = crypto; - long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); - } catch (RemoteException e) { - Slog.w(TAG, "Remote exception while authenticating: ", e); - if (callback != null) { + if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); + try { + useHandler(handler); + mAuthenticationCallback = callback; + mCryptoObject = crypto; + long sessionId = crypto != null ? crypto.getOpId() : 0; + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, + mContext.getOpPackageName(), handle); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception while authenticating: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try // again later. callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); + 0 /* vendorCode */)); + } finally { + BiometricNativeHandleUtils.close(handle); } } } /** + * Defaults to {@link FingerprintManager#enroll(byte[], CancellationSignal, int, int, + * EnrollmentCallback, NativeHandle)} with {@code windowId} set to null. + * + * @see FingerprintManager#enroll(byte[], CancellationSignal, int, int, EnrollmentCallback, + * NativeHandle) + * + * @hide + */ + @RequiresPermission(MANAGE_FINGERPRINT) + public void enroll(byte [] token, CancellationSignal cancel, int flags, + int userId, EnrollmentCallback callback) { + enroll(token, cancel, flags, userId, callback, null /* windowId */); + } + + /** * Request fingerprint enrollment. This call warms up the fingerprint hardware * and starts scanning for fingerprints. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when @@ -462,7 +501,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(MANAGE_FINGERPRINT) public void enroll(byte [] token, CancellationSignal cancel, int flags, - int userId, EnrollmentCallback callback) { + int userId, EnrollmentCallback callback, @Nullable NativeHandle windowId) { if (userId == UserHandle.USER_CURRENT) { userId = getCurrentUserId(); } @@ -479,18 +518,21 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - if (mService != null) try { - mEnrollmentCallback = callback; - mService.enroll(mToken, token, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); - } catch (RemoteException e) { - Slog.w(TAG, "Remote exception in enroll: ", e); - if (callback != null) { + if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); + try { + mEnrollmentCallback = callback; + mService.enroll(mToken, token, userId, mServiceReceiver, flags, + mContext.getOpPackageName(), handle); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try // again later. callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE, getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); + 0 /* vendorCode */)); + } finally { + BiometricNativeHandleUtils.close(handle); } } } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 1a7e12856753..f2ffd08d5bc8 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintClientActiveCallback; @@ -31,7 +32,8 @@ interface IFingerprintService { // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes // through FingerprintManager now. void authenticate(IBinder token, long sessionId, int userId, - IFingerprintServiceReceiver receiver, int flags, String opPackageName); + IFingerprintServiceReceiver receiver, int flags, String opPackageName, + in IBiometricNativeHandle windowId); // This method prepares the service to start authenticating, but doesn't start authentication. // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be @@ -40,7 +42,7 @@ interface IFingerprintService { // startPreparedClient(). void prepareForAuthentication(IBinder token, long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, in IBiometricNativeHandle windowId); // Starts authentication with the previously prepared client. void startPreparedClient(int cookie); @@ -55,7 +57,7 @@ interface IFingerprintService { // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, - int flags, String opPackageName); + int flags, String opPackageName, in IBiometricNativeHandle windowId); // Cancel enrollment in progress void cancelEnrollment(IBinder token); diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index a30fd6b51e76..dbf33cade60c 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -17,7 +17,6 @@ package android.hardware.soundtrigger; import android.annotation.Nullable; -import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; import android.media.soundtrigger_middleware.AudioCapabilities; @@ -333,20 +332,22 @@ class ConversionUtil { public static int aidl2apiAudioCapabilities(int aidlCapabilities) { int result = 0; if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) { - result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; } if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) { - result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; } return result; } public static int api2aidlAudioCapabilities(int apiCapabilities) { int result = 0; - if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) { + if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION) + != 0) { result |= AudioCapabilities.ECHO_CANCELLATION; } - if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) { + if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION) + != 0) { result |= AudioCapabilities.NOISE_SUPPRESSION; } return result; diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index d505ae59dfaf..a74871d29041 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -97,8 +97,8 @@ public class SoundTrigger { */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { - CAPABILITY_ECHO_CANCELLATION, - CAPABILITY_NOISE_SUPPRESSION + AUDIO_CAPABILITY_ECHO_CANCELLATION, + AUDIO_CAPABILITY_NOISE_SUPPRESSION }) public @interface AudioCapabilities {} @@ -106,12 +106,12 @@ public class SoundTrigger { * If set the underlying module supports AEC. * Describes bit field {@link ModuleProperties#audioCapabilities} */ - public static final int CAPABILITY_ECHO_CANCELLATION = 0x1; + public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 0x1; /** * If set, the underlying module supports noise suppression. * Describes bit field {@link ModuleProperties#audioCapabilities} */ - public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2; + public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 0x2; /** Unique module ID provided by the native service */ public final int id; @@ -735,22 +735,40 @@ public class SoundTrigger { /** * The inclusive start of supported range. */ - public final int start; + private final int mStart; /** * The inclusive end of supported range. */ - public final int end; + private final int mEnd; ModelParamRange(int start, int end) { - this.start = start; - this.end = end; + this.mStart = start; + this.mEnd = end; } /** @hide */ private ModelParamRange(@NonNull Parcel in) { - this.start = in.readInt(); - this.end = in.readInt(); + this.mStart = in.readInt(); + this.mEnd = in.readInt(); + } + + /** + * Get the beginning of the param range + * + * @return The inclusive start of the supported range. + */ + public int getStart() { + return mStart; + } + + /** + * Get the end of the param range + * + * @return The inclusive end of the supported range. + */ + public int getEnd() { + return mEnd; } @NonNull @@ -780,8 +798,8 @@ public class SoundTrigger { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (start); - result = prime * result + (end); + result = prime * result + (mStart); + result = prime * result + (mEnd); return result; } @@ -797,10 +815,10 @@ public class SoundTrigger { return false; } ModelParamRange other = (ModelParamRange) obj; - if (start != other.start) { + if (mStart != other.mStart) { return false; } - if (end != other.end) { + if (mEnd != other.mEnd) { return false; } return true; @@ -808,14 +826,14 @@ public class SoundTrigger { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(start); - dest.writeInt(end); + dest.writeInt(mStart); + dest.writeInt(mEnd); } @Override @NonNull public String toString() { - return "ModelParamRange [start=" + start + ", end=" + end + "]"; + return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; } } diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 140363c48227..b128ea7f3e39 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -676,7 +676,8 @@ public class ConnectivityDiagnosticsManager { } try { - mService.registerConnectivityDiagnosticsCallback(binder, request); + mService.registerConnectivityDiagnosticsCallback( + binder, request, mContext.getOpPackageName()); } catch (RemoteException exception) { exception.rethrowFromSystemServer(); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fa12c08f2277..f644f148a5ad 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3750,6 +3750,7 @@ public class ConnectivityManager { checkCallbackNotNull(callback); Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities"); final NetworkRequest request; + final String callingPackageName = mContext.getOpPackageName(); try { synchronized(sCallbacks) { if (callback.networkRequest != null @@ -3761,10 +3762,11 @@ public class ConnectivityManager { Messenger messenger = new Messenger(handler); Binder binder = new Binder(); if (action == LISTEN) { - request = mService.listenForNetwork(need, messenger, binder); + request = mService.listenForNetwork( + need, messenger, binder, callingPackageName); } else { request = mService.requestNetwork( - need, messenger, timeoutMs, binder, legacyType); + need, messenger, timeoutMs, binder, legacyType, callingPackageName); } if (request != null) { sCallbacks.put(request, callback); @@ -4037,8 +4039,10 @@ public class ConnectivityManager { @NonNull PendingIntent operation) { printStackTrace(); checkPendingIntentNotNull(operation); + final String callingPackageName = mContext.getOpPackageName(); try { - mService.pendingRequestForNetwork(request.networkCapabilities, operation); + mService.pendingRequestForNetwork( + request.networkCapabilities, operation, callingPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { @@ -4150,8 +4154,10 @@ public class ConnectivityManager { @NonNull PendingIntent operation) { printStackTrace(); checkPendingIntentNotNull(operation); + final String callingPackageName = mContext.getOpPackageName(); try { - mService.pendingListenForNetwork(request.networkCapabilities, operation); + mService.pendingListenForNetwork( + request.networkCapabilities, operation, callingPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 1089a197ff59..1c7628f6ad0a 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -167,18 +167,19 @@ interface IConnectivityManager in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, + String callingPackageName); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, - in PendingIntent operation); + in PendingIntent operation, String callingPackageName); void releasePendingNetworkRequest(in PendingIntent operation); NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, in IBinder binder); + in Messenger messenger, in IBinder binder, String callingPackageName); void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, - in PendingIntent operation); + in PendingIntent operation, String callingPackageName); void releaseNetworkRequest(in NetworkRequest networkRequest); @@ -222,7 +223,7 @@ interface IConnectivityManager boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, - in NetworkRequest request); + in NetworkRequest request, String callingPackageName); void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback); IBinder startOrGetTestNetworkService(); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 4f4e27b446ef..f8b51dd9906b 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -27,6 +27,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; +import android.text.TextUtils; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; @@ -63,6 +64,16 @@ public final class NetworkCapabilities implements Parcelable { // Set to true when private DNS is broken. private boolean mPrivateDnsBroken; + /** + * Uid of the app making the request. + */ + private int mRequestorUid; + + /** + * Package name of the app making the request. + */ + private String mRequestorPackageName; + public NetworkCapabilities() { clearAll(); mNetworkCapabilities = DEFAULT_CAPABILITIES; @@ -89,6 +100,8 @@ public final class NetworkCapabilities implements Parcelable { mOwnerUid = Process.INVALID_UID; mSSID = null; mPrivateDnsBroken = false; + mRequestorUid = Process.INVALID_UID; + mRequestorPackageName = null; } /** @@ -109,6 +122,8 @@ public final class NetworkCapabilities implements Parcelable { mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; mPrivateDnsBroken = nc.mPrivateDnsBroken; + mRequestorUid = nc.mRequestorUid; + mRequestorPackageName = nc.mRequestorPackageName; } /** @@ -810,7 +825,7 @@ public final class NetworkCapabilities implements Parcelable { } /** - * UID of the app that owns this network, or INVALID_UID if none/unknown. + * UID of the app that owns this network, or Process#INVALID_UID if none/unknown. * * <p>This field keeps track of the UID of the app that created this network and is in charge of * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running @@ -821,8 +836,9 @@ public final class NetworkCapabilities implements Parcelable { /** * Set the UID of the owner app. */ - public void setOwnerUid(final int uid) { + public @NonNull NetworkCapabilities setOwnerUid(final int uid) { mOwnerUid = uid; + return this; } /** @@ -858,16 +874,18 @@ public final class NetworkCapabilities implements Parcelable { * * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator. * - * <p>An app is granted owner privileges over Networks that it supplies. Owner privileges - * implicitly include administrator privileges. + * <p>An app is granted owner privileges over Networks that it supplies. The owner UID MUST + * always be included in administratorUids. * * @param administratorUids the UIDs to be set as administrators of this Network. * @hide */ @SystemApi - public void setAdministratorUids(@NonNull final List<Integer> administratorUids) { + public @NonNull NetworkCapabilities setAdministratorUids( + @NonNull final List<Integer> administratorUids) { mAdministratorUids.clear(); mAdministratorUids.addAll(administratorUids); + return this; } /** @@ -1385,6 +1403,7 @@ public final class NetworkCapabilities implements Parcelable { combineSignalStrength(nc); combineUids(nc); combineSSIDs(nc); + combineRequestor(nc); } /** @@ -1404,7 +1423,8 @@ public final class NetworkCapabilities implements Parcelable { && satisfiedBySpecifier(nc) && (onlyImmutable || satisfiedBySignalStrength(nc)) && (onlyImmutable || satisfiedByUids(nc)) - && (onlyImmutable || satisfiedBySSID(nc))); + && (onlyImmutable || satisfiedBySSID(nc))) + && (onlyImmutable || satisfiedByRequestor(nc)); } /** @@ -1488,7 +1508,7 @@ public final class NetworkCapabilities implements Parcelable { public boolean equals(@Nullable Object obj) { if (obj == null || (obj instanceof NetworkCapabilities == false)) return false; NetworkCapabilities that = (NetworkCapabilities) obj; - return (equalsNetCapabilities(that) + return equalsNetCapabilities(that) && equalsTransportTypes(that) && equalsLinkBandwidths(that) && equalsSignalStrength(that) @@ -1496,7 +1516,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsTransportInfo(that) && equalsUids(that) && equalsSSID(that) - && equalsPrivateDnsBroken(that)); + && equalsPrivateDnsBroken(that) + && equalsRequestor(that); } @Override @@ -1514,7 +1535,9 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mUids) * 31 + Objects.hashCode(mSSID) * 37 + Objects.hashCode(mTransportInfo) * 41 - + Objects.hashCode(mPrivateDnsBroken) * 43; + + Objects.hashCode(mPrivateDnsBroken) * 43 + + Objects.hashCode(mRequestorUid) * 47 + + Objects.hashCode(mRequestorPackageName) * 53; } @Override @@ -1537,6 +1560,8 @@ public final class NetworkCapabilities implements Parcelable { dest.writeBoolean(mPrivateDnsBroken); dest.writeList(mAdministratorUids); dest.writeInt(mOwnerUid); + dest.writeInt(mRequestorUid); + dest.writeString(mRequestorPackageName); } public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR = @@ -1559,6 +1584,8 @@ public final class NetworkCapabilities implements Parcelable { netCap.mPrivateDnsBroken = in.readBoolean(); netCap.setAdministratorUids(in.readArrayList(null)); netCap.mOwnerUid = in.readInt(); + netCap.mRequestorUid = in.readInt(); + netCap.mRequestorPackageName = in.readString(); return netCap; } @Override @@ -1624,6 +1651,9 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" Private DNS is broken"); } + sb.append(" RequestorUid: ").append(mRequestorUid); + sb.append(" RequestorPackageName: ").append(mRequestorPackageName); + sb.append("]"); return sb.toString(); } @@ -1632,6 +1662,7 @@ public final class NetworkCapabilities implements Parcelable { private interface NameOf { String nameOf(int value); } + /** * @hide */ @@ -1799,4 +1830,120 @@ public final class NetworkCapabilities implements Parcelable { private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) { return mPrivateDnsBroken == nc.mPrivateDnsBroken; } + + /** + * Set the uid of the app making the request. + * + * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in + * via the public {@link ConnectivityManager} API's will have this field overwritten. + * + * @param uid UID of the app. + * @hide + */ + @SystemApi + public @NonNull NetworkCapabilities setRequestorUid(int uid) { + mRequestorUid = uid; + return this; + } + + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} + * object was not obtained from {@link ConnectivityManager}. + * @hide + */ + public int getRequestorUid() { + return mRequestorUid; + } + + /** + * Set the package name of the app making the request. + * + * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in + * via the public {@link ConnectivityManager} API's will have this field overwritten. + * + * @param packageName package name of the app. + * @hide + */ + @SystemApi + public @NonNull NetworkCapabilities setRequestorPackageName(@NonNull String packageName) { + mRequestorPackageName = packageName; + return this; + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @Nullable + public String getRequestorPackageName() { + return mRequestorPackageName; + } + + /** + * Set the uid and package name of the app making the request. + * + * Note: This is intended to be only invoked from within connectivitiy service. + * + * @param uid UID of the app. + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUidAndPackageName( + int uid, @NonNull String packageName) { + return setRequestorUid(uid).setRequestorPackageName(packageName); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the requestor restrictions of this + * capabilities. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. If the available network, sets a specific + * requestor (by uid and optionally package name), then this will only match a request from the + * same app. If either of the capabilities have an unset uid or package name, then it matches + * everything. + * <p> + * nc is assumed nonnull. Else, NPE. + */ + private boolean satisfiedByRequestor(NetworkCapabilities nc) { + // No uid set, matches everything. + if (mRequestorUid == Process.INVALID_UID || nc.mRequestorUid == Process.INVALID_UID) { + return true; + } + // uids don't match. + if (mRequestorUid != nc.mRequestorUid) return false; + // No package names set, matches everything + if (null == nc.mRequestorPackageName || null == mRequestorPackageName) return true; + // check for package name match. + return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Combine requestor info of the capabilities. + * <p> + * This is only legal if either the requestor info of this object is reset, or both info are + * equal. + * nc is assumed nonnull. + */ + private void combineRequestor(@NonNull NetworkCapabilities nc) { + if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) { + throw new IllegalStateException("Can't combine two uids"); + } + if (mRequestorPackageName != null + && !mRequestorPackageName.equals(nc.mRequestorPackageName)) { + throw new IllegalStateException("Can't combine two package names"); + } + setRequestorUid(nc.mRequestorUid); + setRequestorPackageName(nc.mRequestorPackageName); + } + + private boolean equalsRequestor(NetworkCapabilities nc) { + return mRequestorUid == nc.mRequestorUid + && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 301d20340643..964f13f39ec6 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -380,6 +380,7 @@ public class NetworkRequest implements Parcelable { dest.writeInt(requestId); dest.writeString(type.name()); } + public static final @android.annotation.NonNull Creator<NetworkRequest> CREATOR = new Creator<NetworkRequest>() { public NetworkRequest createFromParcel(Parcel in) { @@ -494,6 +495,31 @@ public class NetworkRequest implements Parcelable { return networkCapabilities.getNetworkSpecifier(); } + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} object was + * not obtained from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + public int getRequestorUid() { + return networkCapabilities.getRequestorUid(); + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + @Nullable + public String getRequestorPackageName() { + return networkCapabilities.getRequestorPackageName(); + } + public String toString() { return "NetworkRequest [ " + type + " id=" + requestId + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java index cf31d217c967..2dd0c4e207fe 100644 --- a/core/java/android/net/NetworkSpecifier.java +++ b/core/java/android/net/NetworkSpecifier.java @@ -39,23 +39,6 @@ public abstract class NetworkSpecifier { /** * Optional method which can be overridden by concrete implementations of NetworkSpecifier to - * check a self-reported UID. A concrete implementation may contain a UID which would be self- - * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This - * function is called by ConnectivityService and is passed the actual UID of the caller - - * allowing the verification of the self-reported UID. In cases of mismatch the implementation - * should throw a SecurityException. - * - * @param requestorUid The UID of the requestor as obtained from its binder. - * - * @hide - */ - @SystemApi - public void assertValidFromUid(int requestorUid) { - // empty - } - - /** - * Optional method which can be overridden by concrete implementations of NetworkSpecifier to * perform any redaction of information from the NetworkSpecifier, e.g. if it contains * sensitive information. The default implementation simply returns the object itself - i.e. * no information is redacted. A concrete implementation may return a modified (copy) of the diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index a29173469ffb..70b2db70a9e8 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -240,6 +240,13 @@ public class Build { public static final String RELEASE = getString("ro.build.version.release"); /** + * The version string we show to the user; may be {@link #RELEASE} or + * {@link #CODENAME} if not a final release build. + */ + @NonNull public static final String RELEASE_OR_CODENAME = getString( + "ro.build.version.release_or_codename"); + + /** * The base OS build the product is based on. */ public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", ""); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 44f12a6adf60..21a1e0f0a108 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -34,9 +34,11 @@ import android.text.TextUtils; import android.util.Log; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; /** * Provides access to environment variables. @@ -539,12 +541,21 @@ public class Environment { @SystemApi public static @NonNull Collection<File> getInternalMediaDirectories() { final ArrayList<File> res = new ArrayList<>(); - res.add(new File(Environment.getRootDirectory(), "media")); - res.add(new File(Environment.getOemDirectory(), "media")); - res.add(new File(Environment.getProductDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getRootDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getOemDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getProductDirectory(), "media")); return res; } + private static void addCanonicalFile(List<File> list, File file) { + try { + list.add(file.getCanonicalFile()); + } catch (IOException e) { + Log.w(TAG, "Failed to resolve " + file + ": " + e); + list.add(file); + } + } + /** * Return the primary shared/external storage directory. This directory may * not currently be accessible if it has been mounted by the user on their diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index f4c87ac9dfc9..4c4335bc7867 100644 --- a/core/java/android/os/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -18,7 +18,6 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import java.util.Objects; @@ -36,7 +35,6 @@ import java.util.Objects; * @param <T> the type of the value with an associated timestamp * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TimestampedValue<T> implements Parcelable { private final long mReferenceTimeMillis; @Nullable @@ -96,7 +94,6 @@ public final class TimestampedValue<T> implements Parcelable { } /** @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR = new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() { diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 2b6cd1478da8..21434a2aecba 100644 --- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -20,7 +20,7 @@ import android.content.pm.DataLoaderParamsParcel; import android.os.incremental.IncrementalNewFileParams; /** @hide */ -interface IIncrementalManagerNative { +interface IIncrementalService { /** * A set of flags for the |createMode| parameters when creating a new Incremental storage. */ diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 9c6672d8ab68..35fa37a491de 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -23,8 +23,6 @@ import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.os.RemoteException; -import android.system.ErrnoException; -import android.system.Os; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -37,7 +35,7 @@ import java.nio.file.Path; import java.nio.file.Paths; /** - * Provides operations to open or create an IncrementalStorage, using IIncrementalManagerNative + * Provides operations to open or create an IncrementalStorage, using IIncrementalService * service. Example Usage: * * <blockquote><pre> @@ -52,13 +50,13 @@ public final class IncrementalManager { private static final String TAG = "IncrementalManager"; public static final int CREATE_MODE_TEMPORARY_BIND = - IIncrementalManagerNative.CREATE_MODE_TEMPORARY_BIND; + IIncrementalService.CREATE_MODE_TEMPORARY_BIND; public static final int CREATE_MODE_PERMANENT_BIND = - IIncrementalManagerNative.CREATE_MODE_PERMANENT_BIND; + IIncrementalService.CREATE_MODE_PERMANENT_BIND; public static final int CREATE_MODE_CREATE = - IIncrementalManagerNative.CREATE_MODE_CREATE; + IIncrementalService.CREATE_MODE_CREATE; public static final int CREATE_MODE_OPEN_EXISTING = - IIncrementalManagerNative.CREATE_MODE_OPEN_EXISTING; + IIncrementalService.CREATE_MODE_OPEN_EXISTING; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CREATE_MODE_"}, value = { @@ -70,12 +68,12 @@ public final class IncrementalManager { public @interface CreateMode { } - private final @Nullable IIncrementalManagerNative mNativeService; + private final @Nullable IIncrementalService mService; @GuardedBy("mStorages") private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>(); - public IncrementalManager(IIncrementalManagerNative nativeService) { - mNativeService = nativeService; + public IncrementalManager(IIncrementalService service) { + mService = service; } /** @@ -108,11 +106,11 @@ public final class IncrementalManager { @NonNull DataLoaderParams params, @CreateMode int createMode, boolean autoStartDataLoader) { try { - final int id = mNativeService.createStorage(path, params.getData(), createMode); + final int id = mService.createStorage(path, params.getData(), createMode); if (id < 0) { return null; } - final IncrementalStorage storage = new IncrementalStorage(mNativeService, id); + final IncrementalStorage storage = new IncrementalStorage(mService, id); synchronized (mStorages) { mStorages.put(id, storage); } @@ -135,11 +133,11 @@ public final class IncrementalManager { @Nullable public IncrementalStorage openStorage(@NonNull String path) { try { - final int id = mNativeService.openStorage(path); + final int id = mService.openStorage(path); if (id < 0) { return null; } - final IncrementalStorage storage = new IncrementalStorage(mNativeService, id); + final IncrementalStorage storage = new IncrementalStorage(mService, id); synchronized (mStorages) { mStorages.put(id, storage); } @@ -158,12 +156,12 @@ public final class IncrementalManager { public IncrementalStorage createStorage(@NonNull String path, @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) { try { - final int id = mNativeService.createLinkedStorage( + final int id = mService.createLinkedStorage( path, linkedStorage.getId(), createMode); if (id < 0) { return null; } - final IncrementalStorage storage = new IncrementalStorage(mNativeService, id); + final IncrementalStorage storage = new IncrementalStorage(mService, id); synchronized (mStorages) { mStorages.put(id, storage); } @@ -193,116 +191,54 @@ public final class IncrementalManager { } /** - * Renames an Incremental path to a new path. If source path is a file, make a link from the old - * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental - * Storage and bind the new one. - * <ol> - * <li> For renaming a dir, dest dir will be created if not exists, and does not need to - * be on the same Incremental storage as the source. </li> - * <li> For renaming a file, dest file must be on the same Incremental storage as source. - * </li> - * </ol> + * Set up an app's code path. The expected outcome of this method is: + * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory + * of {@code afterCodeFile}. + * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. * - * @param sourcePath Absolute path to the source. Should be the same type as the destPath (file - * or dir). Expected to already exist and is an Incremental path. - * @param destPath Absolute path to the destination. - * @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type - * mismatch (one is file and the other is dir), or 3) source - * path is not on Incremental File System, - * @throws IOException when 1) cannot find the root path of the Incremental storage - * of source, or 2) cannot retrieve the Incremental storage - * instance of the source, or 3) renaming a file, but dest is - * not on the same Incremental Storage, or 4) renaming a dir, - * dest dir does not exist but fails to be created. - * <p> - * TODO(b/136132412): add unit tests + * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. + * Should no longer have any APKs after this method is called. + * Example: /data/app/vmdl*tmp + * @param afterCodeFile Path that should will have APKs after this method is called. Its parent + * directory should be bind-mounted to a directory under /data/incremental. + * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] + * @throws IllegalArgumentException + * @throws IOException + * TODO(b/147371381): add unit tests */ - public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException { - final File source = new File(sourcePath); - final File dest = new File(destPath); - if (!source.exists()) { - throw new IllegalArgumentException("Path not exist: " + sourcePath); + public void renameCodePath(File beforeCodeFile, File afterCodeFile) + throws IllegalArgumentException, IOException { + final String beforeCodePath = beforeCodeFile.getAbsolutePath(); + final String afterCodePathParent = afterCodeFile.getParentFile().getAbsolutePath(); + if (!isIncrementalPath(beforeCodePath)) { + throw new IllegalArgumentException("Not an Incremental path: " + beforeCodePath); } - if (dest.exists()) { - throw new IllegalArgumentException("Target path already exists: " + destPath); + final String afterCodePathName = afterCodeFile.getName(); + final Path apkStoragePath = Paths.get(beforeCodePath); + if (apkStoragePath == null || apkStoragePath.toAbsolutePath() == null) { + throw new IOException("Invalid source storage path for: " + beforeCodePath); } - if (source.isDirectory() && dest.exists() && dest.isFile()) { - throw new IllegalArgumentException( - "Trying to rename a dir but destination is a file: " + destPath); - } - if (source.isFile() && dest.exists() && dest.isDirectory()) { - throw new IllegalArgumentException( - "Trying to rename a file but destination is a dir: " + destPath); - } - if (!isIncrementalPath(sourcePath)) { - throw new IllegalArgumentException("Not an Incremental path: " + sourcePath); - } - - Path storagePath = Paths.get(sourcePath); - if (source.isFile()) { - storagePath = getStoragePathForFile(source); - } - if (storagePath == null || storagePath.toAbsolutePath() == null) { - throw new IOException("Invalid source storage path for: " + sourcePath); - } - final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString()); - if (storage == null) { + final IncrementalStorage apkStorage = + openStorage(apkStoragePath.toAbsolutePath().toString()); + if (apkStorage == null) { throw new IOException("Failed to retrieve storage from Incremental Service."); } - - if (source.isFile()) { - renameFile(storage, storagePath, source, dest); - } else { - renameDir(storage, storagePath, source, dest); - } - } - - private void renameFile(IncrementalStorage storage, Path storagePath, - File source, File dest) throws IOException { - Path sourcePath = source.toPath(); - Path destPath = dest.toPath(); - if (!sourcePath.startsWith(storagePath)) { - throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: " - + storagePath.toString()); - } - if (!destPath.startsWith(storagePath)) { - throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: " - + storagePath.toString()); + final IncrementalStorage linkedApkStorage = createStorage(afterCodePathParent, apkStorage, + IncrementalManager.CREATE_MODE_CREATE + | IncrementalManager.CREATE_MODE_PERMANENT_BIND); + if (linkedApkStorage == null) { + throw new IOException("Failed to create linked storage at dir: " + afterCodePathParent); } - final Path sourceRelativePath = storagePath.relativize(sourcePath); - final Path destRelativePath = storagePath.relativize(destPath); - storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString()); - - } - - private void renameDir(IncrementalStorage storage, Path storagePath, - File source, File dest) throws IOException { - Path destPath = dest.toPath(); - boolean usedMkdir = false; - try { - Os.mkdir(dest.getAbsolutePath(), 0755); - usedMkdir = true; - } catch (ErrnoException e) { - // Traditional mkdir fails but maybe we can create it on Incremental File System if - // the dest path is on the same Incremental storage as the source. - if (destPath.startsWith(storagePath)) { - storage.makeDirectories(storagePath.relativize(destPath).toString()); - } else { - throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e); - } - } - try { - storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath()); - } catch (Exception ex) { - if (usedMkdir) { - try { - Os.remove(dest.getAbsolutePath()); - } catch (ErrnoException ignored) { - } + linkedApkStorage.makeDirectory(afterCodePathName); + File[] files = beforeCodeFile.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + String fileName = files[i].getName(); + apkStorage.makeLink( + fileName, linkedApkStorage, afterCodePathName + "/" + fileName); } - throw new IOException( - "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath()); } + apkStorage.unBind(beforeCodePath); } /** @@ -311,11 +247,11 @@ public final class IncrementalManager { */ public void closeStorage(@NonNull String path) { try { - final int id = mNativeService.openStorage(path); + final int id = mService.openStorage(path); if (id < 0) { return; } - mNativeService.deleteStorage(id); + mService.deleteStorage(id); synchronized (mStorages) { mStorages.remove(id); } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index e5d1b43d5921..c4b843b6ce33 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -40,10 +40,10 @@ import java.util.UUID; public final class IncrementalStorage { private static final String TAG = "IncrementalStorage"; private final int mId; - private final IIncrementalManagerNative mService; + private final IIncrementalService mService; - public IncrementalStorage(@NonNull IIncrementalManagerNative is, int id) { + public IncrementalStorage(@NonNull IIncrementalService is, int id) { mService = is; mId = id; } @@ -74,7 +74,7 @@ public final class IncrementalStorage { throws IOException { try { int res = mService.makeBindMount(mId, sourcePath, targetPath, - IIncrementalManagerNative.BIND_TEMPORARY); + IIncrementalService.BIND_TEMPORARY); if (res < 0) { throw new IOException("bind() failed with errno " + -res); } @@ -105,7 +105,7 @@ public final class IncrementalStorage { throws IOException { try { int res = mService.makeBindMount(mId, sourcePath, targetPath, - IIncrementalManagerNative.BIND_PERMANENT); + IIncrementalService.BIND_PERMANENT); if (res < 0) { throw new IOException("bind() permanent failed with errno " + -res); } @@ -293,7 +293,7 @@ public final class IncrementalStorage { } try { int res = mService.makeBindMount(mId, sourcePath, destPath, - IIncrementalManagerNative.BIND_PERMANENT); + IIncrementalService.BIND_PERMANENT); if (res < 0) { throw new IOException("moveDir() failed at making bind mount, errno " + -res); } diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index b6f5138a6582..adfa885f121e 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5205,6 +5205,7 @@ public final class Telephony { /** * TelephonyProvider column name for allowed network types. Indicate which network types * are allowed. Default is -1. + * <P>Type: BIGINT (long) </P> */ public static final String ALLOWED_NETWORK_TYPES = "allowed_network_types"; } diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 368c94cdc790..79852d3c0fca 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -31,7 +31,6 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.Build; -import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; @@ -93,7 +92,7 @@ public abstract class AugmentedAutofillService extends Service { // Used for metrics / debug only private ComponentName mServiceComponentName; - private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() { + private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub { @Override public void onConnected(boolean debug, boolean verbose) { @@ -137,7 +136,7 @@ public abstract class AugmentedAutofillService extends Service { public final IBinder onBind(Intent intent) { mServiceComponentName = intent.getComponent(); if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); + return new AugmentedAutofillServiceImpl(); } Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; @@ -352,11 +351,13 @@ public abstract class AugmentedAutofillService extends Service { static final int REPORT_EVENT_NO_RESPONSE = 1; static final int REPORT_EVENT_UI_SHOWN = 2; static final int REPORT_EVENT_UI_DESTROYED = 3; + static final int REPORT_EVENT_INLINE_RESPONSE = 4; @IntDef(prefix = { "REPORT_EVENT_" }, value = { REPORT_EVENT_NO_RESPONSE, REPORT_EVENT_UI_SHOWN, - REPORT_EVENT_UI_DESTROYED + REPORT_EVENT_UI_DESTROYED, + REPORT_EVENT_INLINE_RESPONSE }) @Retention(RetentionPolicy.SOURCE) @interface ReportEvent{} @@ -365,8 +366,8 @@ public abstract class AugmentedAutofillService extends Service { private final Object mLock = new Object(); private final IAugmentedAutofillManagerClient mClient; private final int mSessionId; - public final int taskId; - public final ComponentName componentName; + public final int mTaskId; + public final ComponentName mComponentName; // Used for metrics / debug only private String mServicePackageName; @GuardedBy("mLock") @@ -406,8 +407,8 @@ public abstract class AugmentedAutofillService extends Service { mSessionId = sessionId; mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); mCallback = callback; - this.taskId = taskId; - this.componentName = componentName; + mTaskId = taskId; + mComponentName = componentName; mServicePackageName = serviceComponentName.getPackageName(); mFocusedId = focusedId; mFocusedValue = focusedValue; @@ -514,22 +515,24 @@ public abstract class AugmentedAutofillService extends Service { } } - public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + void reportResult(@Nullable List<Dataset> inlineSuggestionsData) { try { - mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState); + final Dataset[] inlineSuggestions = (inlineSuggestionsData != null) + ? inlineSuggestionsData.toArray(new Dataset[inlineSuggestionsData.size()]) + : null; + mCallback.onSuccess(inlineSuggestions); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } } - // Used (mostly) for metrics. - public void report(@ReportEvent int event) { - if (sVerbose) Log.v(TAG, "report(): " + event); + void logEvent(@ReportEvent int event) { + if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event); long duration = -1; int type = MetricsEvent.TYPE_UNKNOWN; + switch (event) { - case REPORT_EVENT_NO_RESPONSE: + case REPORT_EVENT_NO_RESPONSE: { type = MetricsEvent.TYPE_SUCCESS; if (mFirstOnSuccessTime == 0) { mFirstOnSuccessTime = SystemClock.elapsedRealtime(); @@ -538,40 +541,49 @@ public abstract class AugmentedAutofillService extends Service { Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); } } - try { - mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/ - null); - } catch (RemoteException e) { - Log.e(TAG, "Error reporting success: " + e); + } break; + + case REPORT_EVENT_INLINE_RESPONSE: { + // TODO: Define a constant and log this event + // type = MetricsEvent.TYPE_SUCCESS_INLINE; + if (mFirstOnSuccessTime == 0) { + mFirstOnSuccessTime = SystemClock.elapsedRealtime(); + duration = mFirstOnSuccessTime - mFirstRequestTime; + if (sDebug) { + Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); + } } - break; - case REPORT_EVENT_UI_SHOWN: + } break; + + case REPORT_EVENT_UI_SHOWN: { type = MetricsEvent.TYPE_OPEN; if (mUiFirstShownTime == 0) { mUiFirstShownTime = SystemClock.elapsedRealtime(); duration = mUiFirstShownTime - mFirstRequestTime; if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration)); } - break; - case REPORT_EVENT_UI_DESTROYED: + } break; + + case REPORT_EVENT_UI_DESTROYED: { type = MetricsEvent.TYPE_CLOSE; if (mUiFirstDestroyedTime == 0) { mUiFirstDestroyedTime = SystemClock.elapsedRealtime(); - duration = mUiFirstDestroyedTime - mFirstRequestTime; + duration = mUiFirstDestroyedTime - mFirstRequestTime; if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration)); } - break; + } break; + default: Log.w(TAG, "invalid event reported: " + event); } - logResponse(type, mServicePackageName, componentName, mSessionId, duration); + logResponse(type, mServicePackageName, mComponentName, mSessionId, duration); } public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); - pw.print(prefix); pw.print("taskId: "); pw.println(taskId); + pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId); pw.print(prefix); pw.print("component: "); - pw.println(componentName.flattenToShortString()); + pw.println(mComponentName.flattenToShortString()); pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); if (mFocusedValue != null) { pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index d0ffd7b4d8d4..19eff57269ae 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -54,13 +54,15 @@ public final class FillCallback { if (sDebug) Log.d(TAG, "onSuccess(): " + response); if (response == null) { - mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); + mProxy.reportResult(null /*inlineSuggestions*/); return; } List<Dataset> inlineSuggestions = response.getInlineSuggestions(); if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { - mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState()); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); + mProxy.reportResult(inlineSuggestions); return; } diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java index 63ec2d886caf..7d552d62fa72 100644 --- a/core/java/android/service/autofill/augmented/FillController.java +++ b/core/java/android/service/autofill/augmented/FillController.java @@ -62,12 +62,13 @@ public final class FillController { try { mProxy.autofill(values); - final FillWindow fillWindow = mProxy.getFillWindow(); - if (fillWindow != null) { - fillWindow.destroy(); - } } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + final FillWindow fillWindow = mProxy.getFillWindow(); + if (fillWindow != null) { + fillWindow.destroy(); + } } } diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index ca49e7daf054..6927cf6541e0 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -53,7 +53,7 @@ public final class FillRequest { * Gets the task of the activity associated with this request. */ public int getTaskId() { - return mProxy.taskId; + return mProxy.mTaskId; } /** @@ -61,7 +61,7 @@ public final class FillRequest { */ @NonNull public ComponentName getActivityComponent() { - return mProxy.componentName; + return mProxy.mComponentName; } /** diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 5d003706ac83..077df6cf16ef 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -21,6 +21,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.sVerbo import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.graphics.Rect; @@ -41,6 +42,7 @@ import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.PrintWriter; +import java.lang.ref.WeakReference; /** * Handle to a window used to display the augmented autofill UI. @@ -70,23 +72,22 @@ public final class FillWindow implements AutoCloseable { private final CloseGuard mCloseGuard = CloseGuard.get(); private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper()); - private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter(); @GuardedBy("mLock") - private WindowManager mWm; + private @NonNull WindowManager mWm; @GuardedBy("mLock") private View mFillView; @GuardedBy("mLock") private boolean mShowing; @GuardedBy("mLock") - private Rect mBounds; + private @Nullable Rect mBounds; @GuardedBy("mLock") private boolean mUpdateCalled; @GuardedBy("mLock") private boolean mDestroyed; - private AutofillProxy mProxy; + private @NonNull AutofillProxy mProxy; /** * Updates the content of the window. @@ -172,11 +173,11 @@ public final class FillWindow implements AutoCloseable { try { mProxy.requestShowFillUi(mBounds.right - mBounds.left, mBounds.bottom - mBounds.top, - /*anchorBounds=*/ null, mFillWindowPresenter); + /*anchorBounds=*/ null, new FillWindowPresenter(this)); } catch (RemoteException e) { Log.w(TAG, "Error requesting to show fill window", e); } - mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN); } } } @@ -244,7 +245,7 @@ public final class FillWindow implements AutoCloseable { if (mUpdateCalled) { mFillView.setOnClickListener(null); hide(); - mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED); } mDestroyed = true; mCloseGuard.close(); @@ -254,9 +255,7 @@ public final class FillWindow implements AutoCloseable { @Override protected void finalize() throws Throwable { try { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } + mCloseGuard.warnIfOpen(); destroy(); } finally { super.finalize(); @@ -289,22 +288,36 @@ public final class FillWindow implements AutoCloseable { /** @hide */ @Override - public void close() throws Exception { + public void close() { destroy(); } - private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub { + private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub { + private final @NonNull WeakReference<FillWindow> mFillWindowReference; + + FillWindowPresenter(@NonNull FillWindow fillWindow) { + mFillWindowReference = new WeakReference<>(fillWindow); + } + @Override public void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection) { if (sDebug) Log.d(TAG, "FillWindowPresenter.show()"); - mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p)); + final FillWindow fillWindow = mFillWindowReference.get(); + if (fillWindow != null) { + fillWindow.mUiThreadHandler.sendMessage( + obtainMessage(FillWindow::handleShow, fillWindow, p)); + } } @Override public void hide(Rect transitionEpicenter) { if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()"); - mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this)); + final FillWindow fillWindow = mFillWindowReference.get(); + if (fillWindow != null) { + fillWindow.mUiThreadHandler.sendMessage( + obtainMessage(FillWindow::handleHide, fillWindow)); + } } } } diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 31e77f358782..d9837211be19 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -28,7 +28,7 @@ import android.service.autofill.Dataset; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable Dataset[] inlineSuggestionsData); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 3f9462cb971b..af915966e7eb 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -16,6 +16,9 @@ package android.service.notification; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; @@ -99,6 +102,8 @@ public class ZenModeConfig implements Parcelable { private static final boolean DEFAULT_ALLOW_REMINDERS = false; private static final boolean DEFAULT_ALLOW_EVENTS = false; private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true; + private static final boolean DEFAULT_ALLOW_CONV = true; + private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false; private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 0; @@ -120,6 +125,8 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_EVENTS = "events"; private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff"; private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; + private static final String ALLOW_ATT_CONV = "conv"; + private static final String ALLOW_ATT_CONV_FROM = "convFrom"; private static final String DISALLOW_TAG = "disallow"; private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; private static final String STATE_TAG = "state"; @@ -170,6 +177,8 @@ public class ZenModeConfig implements Parcelable { public boolean allowEvents = DEFAULT_ALLOW_EVENTS; public int allowCallsFrom = DEFAULT_CALLS_SOURCE; public int allowMessagesFrom = DEFAULT_SOURCE; + public boolean allowConversations = DEFAULT_ALLOW_CONV; + public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM; public int user = UserHandle.USER_SYSTEM; public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS; public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND; @@ -207,6 +216,8 @@ public class ZenModeConfig implements Parcelable { allowSystem = source.readInt() == 1; suppressedVisualEffects = source.readInt(); areChannelsBypassingDnd = source.readInt() == 1; + allowConversations = source.readBoolean(); + allowConversationsFrom = source.readInt(); } @Override @@ -239,6 +250,8 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowSystem ? 1 : 0); dest.writeInt(suppressedVisualEffects); dest.writeInt(areChannelsBypassingDnd ? 1 : 0); + dest.writeBoolean(allowConversations); + dest.writeInt(allowConversationsFrom); } @Override @@ -253,8 +266,11 @@ public class ZenModeConfig implements Parcelable { .append(",allowCalls=").append(allowCalls) .append(",allowRepeatCallers=").append(allowRepeatCallers) .append(",allowMessages=").append(allowMessages) + .append(",allowConversations=").append(allowConversations) .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) + .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString + (allowConversationsFrom)) .append(",suppressedVisualEffects=").append(suppressedVisualEffects) .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd) .append(",\nautomaticRules=").append(rulesToString()) @@ -431,7 +447,9 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.automaticRules, automaticRules) && Objects.equals(other.manualRule, manualRule) && other.suppressedVisualEffects == suppressedVisualEffects - && other.areChannelsBypassingDnd == areChannelsBypassingDnd; + && other.areChannelsBypassingDnd == areChannelsBypassingDnd + && other.allowConversations == allowConversations + && other.allowConversationsFrom == allowConversationsFrom; } @Override @@ -440,7 +458,8 @@ public class ZenModeConfig implements Parcelable { allowRepeatCallers, allowMessages, allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, user, automaticRules, manualRule, - suppressedVisualEffects, areChannelsBypassingDnd); + suppressedVisualEffects, areChannelsBypassingDnd, allowConversations, + allowConversationsFrom); } private static String toDayList(int[] days) { @@ -518,10 +537,13 @@ public class ZenModeConfig implements Parcelable { DEFAULT_ALLOW_MESSAGES); rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, DEFAULT_ALLOW_REMINDERS); + rt.allowConversations = safeBoolean(parser, ALLOW_ATT_CONV, DEFAULT_ALLOW_CONV); rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); final int from = safeInt(parser, ALLOW_ATT_FROM, -1); final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); + rt.allowConversationsFrom = safeInt(parser, ALLOW_ATT_CONV_FROM, + DEFAULT_ALLOW_CONV_FROM); if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { rt.allowCallsFrom = callsFrom; rt.allowMessagesFrom = messagesFrom; @@ -602,6 +624,8 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms)); out.attribute(null, ALLOW_ATT_MEDIA, Boolean.toString(allowMedia)); out.attribute(null, ALLOW_ATT_SYSTEM, Boolean.toString(allowSystem)); + out.attribute(null, ALLOW_ATT_CONV, Boolean.toString(allowConversations)); + out.attribute(null, ALLOW_ATT_CONV_FROM, Integer.toString(allowConversationsFrom)); out.endTag(null, ALLOW_TAG); out.startTag(null, DISALLOW_TAG); @@ -944,6 +968,7 @@ public class ZenModeConfig implements Parcelable { int suppressedVisualEffects = 0; int callSenders = defaultPolicy.priorityCallSenders; int messageSenders = defaultPolicy.priorityMessageSenders; + int conversationSenders = defaultPolicy.priorityConversationSenders; if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REMINDERS, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REMINDERS, defaultPolicy))) { @@ -962,6 +987,14 @@ public class ZenModeConfig implements Parcelable { messageSenders); } + if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS, + isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) { + priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; + conversationSenders = getNotificationPolicySenders( + zenPolicy.getPriorityConversationSenders(), + conversationSenders); + } + if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; @@ -1047,7 +1080,7 @@ public class ZenModeConfig implements Parcelable { } return new NotificationManager.Policy(priorityCategories, callSenders, - messageSenders, suppressedVisualEffects, defaultPolicy.state); + messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders); } private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) { @@ -1088,11 +1121,14 @@ public class ZenModeConfig implements Parcelable { } } - public Policy toNotificationPolicy() { int priorityCategories = 0; int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; + int priorityConversationSenders = Policy.CONVERSATION_SENDERS_IMPORTANT; + if (allowConversations) { + priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; + } if (allowCalls) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; } @@ -1119,10 +1155,12 @@ public class ZenModeConfig implements Parcelable { } priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); + priorityConversationSenders = allowConversationsFrom; return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, suppressedVisualEffects, areChannelsBypassingDnd - ? Policy.STATE_CHANNELS_BYPASSING_DND : 0); + ? Policy.STATE_CHANNELS_BYPASSING_DND : 0, + priorityConversationSenders); } /** @@ -1157,6 +1195,27 @@ public class ZenModeConfig implements Parcelable { } } + private static int normalizePrioritySenders(int prioritySenders, int def) { + if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS + || prioritySenders == Policy.PRIORITY_SENDERS_STARRED + || prioritySenders == Policy.PRIORITY_SENDERS_ANY)) { + return def; + } + return prioritySenders; + } + + private static int normalizeConversationSenders(boolean allowed, int senders, int def) { + if (!allowed) { + return CONVERSATION_SENDERS_NONE; + } + if (!(senders == CONVERSATION_SENDERS_ANYONE + || senders == CONVERSATION_SENDERS_IMPORTANT + || senders == CONVERSATION_SENDERS_NONE)) { + return def; + } + return senders; + } + public void applyNotificationPolicy(Policy policy) { if (policy == null) return; allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0; @@ -1168,12 +1227,17 @@ public class ZenModeConfig implements Parcelable { allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; - allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom); - allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders, + allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom); + allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders, allowMessagesFrom); if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) { suppressedVisualEffects = policy.suppressedVisualEffects; } + allowConversations = (policy.priorityCategories + & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0; + allowConversationsFrom = normalizeConversationSenders(allowConversations, + policy.priorityConversationSenders, + allowConversationsFrom); if (policy.state != Policy.STATE_UNSET) { areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; } @@ -1919,10 +1983,13 @@ public class ZenModeConfig implements Parcelable { & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0; boolean allowRepeatCallers = (policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; + boolean allowConversations = (policy.priorityConversationSenders + & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0; boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0; return !allowReminders && !allowCalls && !allowMessages && !allowEvents - && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem; + && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem + && !allowConversations; } /** diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 6e2faa9932ca..87295e1c95b9 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -41,6 +41,7 @@ public final class ZenPolicy implements Parcelable { private ArrayList<Integer> mVisualEffects; private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET; private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET; + private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET; /** @hide */ @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = { @@ -52,6 +53,7 @@ public final class ZenPolicy implements Parcelable { PRIORITY_CATEGORY_ALARMS, PRIORITY_CATEGORY_MEDIA, PRIORITY_CATEGORY_SYSTEM, + PRIORITY_CATEGORY_CONVERSATIONS, }) @Retention(RetentionPolicy.SOURCE) public @interface PriorityCategory {} @@ -72,6 +74,8 @@ public final class ZenPolicy implements Parcelable { public static final int PRIORITY_CATEGORY_MEDIA = 6; /** @hide */ public static final int PRIORITY_CATEGORY_SYSTEM = 7; + /** @hide */ + public static final int PRIORITY_CATEGORY_CONVERSATIONS = 8; /** @hide */ @IntDef(prefix = { "VISUAL_EFFECT_" }, value = { @@ -138,6 +142,37 @@ public final class ZenPolicy implements Parcelable { */ public static final int PEOPLE_TYPE_NONE = 4; + + /** @hide */ + @IntDef(prefix = { "CONVERSATION_SENDERS_" }, value = { + CONVERSATION_SENDERS_UNSET, + CONVERSATION_SENDERS_ANYONE, + CONVERSATION_SENDERS_IMPORTANT, + CONVERSATION_SENDERS_NONE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConversationSenders {} + + /** + * Used to indicate no preference for the type of conversations that can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_UNSET = 0; + + /** + * Used to indicate all conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_ANYONE = 1; + + /** + * Used to indicate important conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_IMPORTANT = 2; + + /** + * Used to indicate no conversations can bypass dnd. + */ + public static final int CONVERSATION_SENDERS_NONE = 3; + /** @hide */ @IntDef(prefix = { "STATE_" }, value = { STATE_UNSET, @@ -165,11 +200,20 @@ public final class ZenPolicy implements Parcelable { /** @hide */ public ZenPolicy() { - mPriorityCategories = new ArrayList<>(Collections.nCopies(8, 0)); + mPriorityCategories = new ArrayList<>(Collections.nCopies(9, 0)); mVisualEffects = new ArrayList<>(Collections.nCopies(7, 0)); } /** + * Conversation type that can bypass DND. + * @return {@link #CONVERSATION_SENDERS_UNSET}, {@link #CONVERSATION_SENDERS_ANYONE}, + * {@link #CONVERSATION_SENDERS_IMPORTANT}, {@link #CONVERSATION_SENDERS_NONE}. + */ + public @PeopleType int getPriorityConversationSenders() { + return mConversationSenders; + } + + /** * Message senders that can bypass DND. * @return {@link #PEOPLE_TYPE_UNSET}, {@link #PEOPLE_TYPE_ANYONE}, * {@link #PEOPLE_TYPE_CONTACTS}, {@link #PEOPLE_TYPE_STARRED} or {@link #PEOPLE_TYPE_NONE} @@ -188,6 +232,16 @@ public final class ZenPolicy implements Parcelable { } /** + * Whether this policy wants to allow conversation notifications + * (see {@link NotificationChannel#getConversationId()}) to play sounds and visually appear + * or to intercept them when DND is active. + * @return {@link #STATE_UNSET}, {@link #STATE_ALLOW} or {@link #STATE_DISALLOW} + */ + public @State int getPriorityCategoryConversations() { + return mPriorityCategories.get(PRIORITY_CATEGORY_CONVERSATIONS); + } + + /** * Whether this policy wants to allow notifications with category * {@link Notification#CATEGORY_REMINDER} to play sounds and visually appear * or to intercept them when DND is active. @@ -392,6 +446,7 @@ public final class ZenPolicy implements Parcelable { } mZenPolicy.mPriorityMessages = PEOPLE_TYPE_ANYONE; mZenPolicy.mPriorityCalls = PEOPLE_TYPE_ANYONE; + mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_ANYONE; return this; } @@ -408,6 +463,7 @@ public final class ZenPolicy implements Parcelable { } mZenPolicy.mPriorityMessages = PEOPLE_TYPE_NONE; mZenPolicy.mPriorityCalls = PEOPLE_TYPE_NONE; + mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_NONE; return this; } @@ -443,6 +499,8 @@ public final class ZenPolicy implements Parcelable { mZenPolicy.mPriorityMessages = STATE_UNSET; } else if (category == PRIORITY_CATEGORY_CALLS) { mZenPolicy.mPriorityCalls = STATE_UNSET; + } else if (category == PRIORITY_CATEGORY_CONVERSATIONS) { + mZenPolicy.mConversationSenders = STATE_UNSET; } return this; @@ -479,6 +537,31 @@ public final class ZenPolicy implements Parcelable { } /** + * Whether to allow conversation notifications + * (see {@link NotificationChannel#setConversationId(String, String)}) + * that match audienceType to play sounds and visually appear or to intercept + * them when DND is active. + * @param audienceType callers that are allowed to bypass DND + */ + public @NonNull Builder allowConversations(@ConversationSenders int audienceType) { + if (audienceType == STATE_UNSET) { + return unsetPriorityCategory(PRIORITY_CATEGORY_CONVERSATIONS); + } + + if (audienceType == CONVERSATION_SENDERS_NONE) { + mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_CONVERSATIONS, STATE_DISALLOW); + } else if (audienceType == CONVERSATION_SENDERS_ANYONE + || audienceType == CONVERSATION_SENDERS_IMPORTANT) { + mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_CONVERSATIONS, STATE_ALLOW); + } else { + return this; + } + + mZenPolicy.mConversationSenders = audienceType; + return this; + } + + /** * Whether to allow notifications with category {@link Notification#CATEGORY_MESSAGE} * that match audienceType to play sounds and visually appear or to intercept * them when DND is active. @@ -537,7 +620,6 @@ public final class ZenPolicy implements Parcelable { return this; } - /** * Whether to allow notifications with category {@link Notification#CATEGORY_ALARM} * to play sounds and visually appear or to intercept them when DND is active. @@ -712,6 +794,7 @@ public final class ZenPolicy implements Parcelable { dest.writeList(mVisualEffects); dest.writeInt(mPriorityCalls); dest.writeInt(mPriorityMessages); + dest.writeInt(mConversationSenders); } public static final @android.annotation.NonNull Parcelable.Creator<ZenPolicy> CREATOR = @@ -723,6 +806,7 @@ public final class ZenPolicy implements Parcelable { policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader()); policy.mPriorityCalls = source.readInt(); policy.mPriorityMessages = source.readInt(); + policy.mConversationSenders = source.readInt(); return policy; } @@ -738,8 +822,10 @@ public final class ZenPolicy implements Parcelable { .append('{') .append("priorityCategories=[").append(priorityCategoriesToString()) .append("], visualEffects=[").append(visualEffectsToString()) - .append("], priorityCalls=").append(peopleTypeToString(mPriorityCalls)) - .append(", priorityMessages=").append(peopleTypeToString(mPriorityMessages)) + .append("], priorityCallsSenders=").append(peopleTypeToString(mPriorityCalls)) + .append(", priorityMessagesSenders=").append(peopleTypeToString(mPriorityMessages)) + .append(", priorityConversationSenders=").append( + conversationTypeToString(mConversationSenders)) .append('}') .toString(); } @@ -811,6 +897,8 @@ public final class ZenPolicy implements Parcelable { return "media"; case PRIORITY_CATEGORY_SYSTEM: return "system"; + case PRIORITY_CATEGORY_CONVERSATIONS: + return "convs"; } return null; } @@ -843,6 +931,23 @@ public final class ZenPolicy implements Parcelable { return "invalidPeopleType{" + peopleType + "}"; } + /** + * @hide + */ + public static String conversationTypeToString(@ConversationSenders int conversationType) { + switch (conversationType) { + case CONVERSATION_SENDERS_ANYONE: + return "anyone"; + case CONVERSATION_SENDERS_IMPORTANT: + return "important"; + case CONVERSATION_SENDERS_NONE: + return "none"; + case CONVERSATION_SENDERS_UNSET: + return "unset"; + } + return "invalidConversationType{" + conversationType + "}"; + } + @Override public boolean equals(Object o) { if (!(o instanceof ZenPolicy)) return false; @@ -852,12 +957,14 @@ public final class ZenPolicy implements Parcelable { return Objects.equals(other.mPriorityCategories, mPriorityCategories) && Objects.equals(other.mVisualEffects, mVisualEffects) && other.mPriorityCalls == mPriorityCalls - && other.mPriorityMessages == mPriorityMessages; + && other.mPriorityMessages == mPriorityMessages + && other.mConversationSenders == mConversationSenders; } @Override public int hashCode() { - return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages); + return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages, + mConversationSenders); } private @ZenPolicy.State int getZenPolicyPriorityCategoryState(@PriorityCategory int @@ -879,6 +986,8 @@ public final class ZenPolicy implements Parcelable { return getPriorityCategoryMedia(); case PRIORITY_CATEGORY_SYSTEM: return getPriorityCategorySystem(); + case PRIORITY_CATEGORY_CONVERSATIONS: + return getPriorityCategoryConversations(); } return -1; } @@ -953,6 +1062,9 @@ public final class ZenPolicy implements Parcelable { } else if (category == PRIORITY_CATEGORY_CALLS && mPriorityCalls < policyToApply.mPriorityCalls) { mPriorityCalls = policyToApply.mPriorityCalls; + } else if (category == PRIORITY_CATEGORY_CONVERSATIONS + && mConversationSenders < policyToApply.mConversationSenders) { + mConversationSenders = policyToApply.mConversationSenders; } } } diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 8dca69f856e5..3ff6f549e337 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -27,7 +27,6 @@ import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; @@ -394,21 +393,39 @@ public abstract class TextClassifierService extends Service { */ @Deprecated public final TextClassifier getLocalTextClassifier() { - // Deprecated: In the future, we may not guarantee that this runs in the service's process. - return getDefaultTextClassifierImplementation(this); + return TextClassifier.NO_OP; } /** * Returns the platform's default TextClassifier implementation. + * + * @throws RuntimeException if the TextClassifier from + * PackageManager#getDefaultTextClassifierPackageName() calls + * this method. */ @NonNull public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) { final TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class); - if (tcm != null) { + if (tcm == null) { + return TextClassifier.NO_OP; + } + TextClassificationConstants settings = new TextClassificationConstants(); + if (settings.getUseDefaultTextClassifierAsDefaultImplementation()) { + final String defaultTextClassifierPackageName = + context.getPackageManager().getDefaultTextClassifierPackageName(); + if (TextUtils.isEmpty(defaultTextClassifierPackageName)) { + return TextClassifier.NO_OP; + } + if (defaultTextClassifierPackageName.equals(context.getPackageName())) { + throw new RuntimeException( + "The default text classifier itself should not call the" + + "getDefaultTextClassifierImplementation() method."); + } + return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE); + } else { return tcm.getTextClassifier(TextClassifier.LOCAL); } - return TextClassifier.NO_OP; } /** @hide **/ @@ -434,46 +451,20 @@ public abstract class TextClassifierService extends Service { } /** - * Returns the component name of the system default textclassifier service if it can be found - * on the system. Otherwise, returns null. + * Returns the component name of the textclassifier service from the given package. + * Otherwise, returns null. * - * @param context the text classification context + * @param context + * @param packageName the package to look for. + * @param resolveFlags the flags that are used by PackageManager to resolve the component name. * @hide */ @Nullable - public static ComponentName getServiceComponentName(@NonNull Context context) { - final TextClassificationConstants settings = TextClassificationManager.getSettings(context); - // get override TextClassifierService package name - String packageName = settings.getTextClassifierServicePackageOverride(); - - ComponentName serviceComponent = null; - final boolean isOverrideService = !TextUtils.isEmpty(packageName); - if (isOverrideService) { - serviceComponent = getServiceComponentNameByPackage(context, packageName, - isOverrideService); - } - if (serviceComponent != null) { - return serviceComponent; - } - // If no TextClassifierService override or invalid override package name, read the first - // package defined in the config - final String[] packages = context.getPackageManager().getSystemTextClassifierPackages(); - if (packages.length == 0 || TextUtils.isEmpty(packages[0])) { - Slog.d(LOG_TAG, "No configured system TextClassifierService"); - return null; - } - packageName = packages[0]; - serviceComponent = getServiceComponentNameByPackage(context, packageName, - isOverrideService); - return serviceComponent; - } - - private static ComponentName getServiceComponentNameByPackage(Context context, - String packageName, boolean isOverrideService) { + public static ComponentName getServiceComponentName( + Context context, String packageName, int resolveFlags) { final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName); - final int flags = isOverrideService ? 0 : PackageManager.MATCH_SYSTEM_ONLY; - final ResolveInfo ri = context.getPackageManager().resolveService(intent, flags); + final ResolveInfo ri = context.getPackageManager().resolveService(intent, resolveFlags); if ((ri == null) || (ri.serviceInfo == null)) { Slog.w(LOG_TAG, String.format("Package or service not found in package %s for user %d", diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 1966f17aaf35..a88d389178bf 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -181,14 +181,14 @@ public class AlwaysOnHotwordDetector { * Returned by {@link #getSupportedAudioCapabilities()} */ public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = - SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; /** * If set, the underlying module supports noise suppression. * Returned by {@link #getSupportedAudioCapabilities()} */ public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = - SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -249,21 +249,21 @@ public class AlwaysOnHotwordDetector { } /** - * The inclusive start of supported range. + * Get the beginning of the param range * - * @return start of range + * @return The inclusive start of the supported range. */ - public int start() { - return mModelParamRange.start; + public int getStart() { + return mModelParamRange.getStart(); } /** - * The inclusive end of supported range. + * Get the end of the param range * - * @return end of range + * @return The inclusive end of the supported range. */ - public int end() { - return mModelParamRange.end; + public int getEnd() { + return mModelParamRange.getEnd(); } @Override diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java index 970acd0a5b3f..ee3a8a79d5d7 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -18,8 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; import android.icu.util.TimeZone; import java.util.ArrayList; @@ -32,7 +30,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class CountryTimeZones { /** @@ -40,7 +37,6 @@ public final class CountryTimeZones { * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class TimeZoneMapping { @NonNull @@ -97,7 +93,6 @@ public final class CountryTimeZones { * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class OffsetResult { private final TimeZone mTimeZone; @@ -210,27 +205,47 @@ public final class CountryTimeZones { } /** - * Returns a time zone for the country, if there is one, that matches the desired properties. If - * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise - * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)} - * ordering. + * Returns a time zone for the country, if there is one, that matches the supplied properties. + * If there are multiple matches and the {@code bias} is one of them then it is returned, + * otherwise an arbitrary match is returned based on the {@link + * #getEffectiveTimeZoneMappingsAt(long)} ordering. * + * @param whenMillis the UTC time to match against + * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference * @param totalOffsetMillis the offset from UTC at {@code whenMillis} * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST, - * {@code false} means not DST, {@code null} means unknown - * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if - * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is - * unknown - * @param whenMillis the UTC time to match against - * @param bias the time zone to prefer, can be {@code null} + * {@code false} means not DST + * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if + * there is no match */ @Nullable - public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst, - @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis, - @Nullable TimeZone bias) { + public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, + int totalOffsetMillis, boolean isDst) { libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = mDelegate.lookupByOffsetWithBias( - totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias); + whenMillis, bias, totalOffsetMillis, isDst); + return delegateOffsetResult == null ? null : + new OffsetResult( + delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); + } + + /** + * Returns a time zone for the country, if there is one, that matches the supplied properties. + * If there are multiple matches and the {@code bias} is one of them then it is returned, + * otherwise an arbitrary match is returned based on the {@link + * #getEffectiveTimeZoneMappingsAt(long)} ordering. + * + * @param whenMillis the UTC time to match against + * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference + * @param totalOffsetMillis the offset from UTC at {@code whenMillis} + * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if + * there is no match + */ + @Nullable + public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, + int totalOffsetMillis) { + libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = + mDelegate.lookupByOffsetWithBias(whenMillis, bias, totalOffsetMillis); return delegateOffsetResult == null ? null : new OffsetResult( delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java index 8a5864e92db1..a4c3fbd33410 100644 --- a/core/java/android/timezone/TelephonyLookup.java +++ b/core/java/android/timezone/TelephonyLookup.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; @@ -29,7 +28,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TelephonyLookup { private static final Object sLock = new Object(); diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java index 487b3f2f143b..823cd251fbf0 100644 --- a/core/java/android/timezone/TelephonyNetwork.java +++ b/core/java/android/timezone/TelephonyNetwork.java @@ -17,7 +17,6 @@ package android.timezone; import android.annotation.NonNull; -import android.annotation.SystemApi; import java.util.Objects; @@ -26,7 +25,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TelephonyNetwork { @NonNull diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java index 2ddd3d998d8e..4bfeff8a73ad 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import java.util.Objects; @@ -27,7 +26,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TelephonyNetworkFinder { @NonNull diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java index c76bb1d1fd28..03f5013f230c 100644 --- a/core/java/android/timezone/TimeZoneFinder.java +++ b/core/java/android/timezone/TimeZoneFinder.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; @@ -29,7 +28,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TimeZoneFinder { private static final Object sLock = new Object(); diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java index efe50a07da98..f993012aeb1c 100644 --- a/core/java/android/timezone/TzDataSetVersion.java +++ b/core/java/android/timezone/TzDataSetVersion.java @@ -17,7 +17,6 @@ package android.timezone; import android.annotation.NonNull; -import android.annotation.SystemApi; import com.android.internal.annotations.VisibleForTesting; @@ -45,7 +44,6 @@ import java.util.Objects; * @hide */ @VisibleForTesting -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TzDataSetVersion { /** @@ -88,7 +86,6 @@ public final class TzDataSetVersion { * A checked exception used in connection with time zone data sets. * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class TzDataSetException extends Exception { /** Creates an instance with a message. */ diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java index 4612a56df117..9354a695812d 100644 --- a/core/java/android/timezone/ZoneInfoDb.java +++ b/core/java/android/timezone/ZoneInfoDb.java @@ -17,7 +17,6 @@ package android.timezone; import android.annotation.NonNull; -import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; @@ -29,7 +28,6 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class ZoneInfoDb { private static final Object sLock = new Object(); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 36f4e5361ea6..4b3afbaada64 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -80,7 +80,7 @@ public class TimeUtils { return null; } CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias( - offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias); + whenMillis, bias, offsetMillis, isDst); return offsetResult != null ? offsetResult.getTimeZone() : null; } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index d79fc9a48116..f61217d60686 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1514,6 +1514,7 @@ public final class Display { public HdrCapabilities(int[] supportedHdrTypes, float maxLuminance, float maxAverageLuminance, float minLuminance) { mSupportedHdrTypes = supportedHdrTypes; + Arrays.sort(mSupportedHdrTypes); mMaxLuminance = maxLuminance; mMaxAverageLuminance = maxAverageLuminance; mMinLuminance = minLuminance; diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 22d6f37dbd95..54de1bb3739d 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -190,9 +190,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); - setStartingAnimation(true); mAnimator.start(); - setStartingAnimation(false); } @Override @@ -203,9 +201,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - protected void setStartingAnimation(boolean startingAnimation) { - } - protected void onAnimationFinish() { mController.finish(mShow); } @@ -239,16 +234,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @AnimationType int type; } - private class DefaultAnimationControlListener extends InternalAnimationControlListener { - DefaultAnimationControlListener(boolean show) { - super(show); - } - - @Override - protected void setStartingAnimation(boolean startingAnimation) { - mStartingAnimation = startingAnimation; - } - } /** * Represents a control request that we had to defer because we are waiting for the IME to * process our show request. @@ -822,7 +807,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } - final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show); + final InternalAnimationControlListener listener = + new InternalAnimationControlListener(show); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. controlAnimationUnchecked( @@ -878,7 +864,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return true; } mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); + mStartingAnimation = true; listener.onReady(controller, types); + mStartingAnimation = false; return true; } }); diff --git a/core/java/android/view/SurfaceControlViewHost.aidl b/core/java/android/view/SurfaceControlViewHost.aidl new file mode 100644 index 000000000000..3b31ab834a51 --- /dev/null +++ b/core/java/android/view/SurfaceControlViewHost.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable SurfaceControlViewHost.SurfacePackage;
\ No newline at end of file diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 75d5538faedd..b1c354f6f717 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1570,7 +1570,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { // TODO: Link accessibility IDs here. - t.reparent(p.getSurfaceControl(), mSurfaceControl); + final SurfaceControl sc = p.getSurfaceControl(); + t.reparent(sc, mSurfaceControl).show(sc); } /** diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index b9f08ada3152..b4c87953567b 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -20,7 +20,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.graphics.Bitmap; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -829,31 +828,6 @@ public final class AccessibilityInteractionClient } /** - * Takes the screenshot of the specified display and returns it by bitmap format. - * - * @param connectionId The id of a connection for interacting with the system. - * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for - * default display. - * @return The screenshot bitmap on success, null otherwise. - */ - public Bitmap takeScreenshot(int connectionId, int displayId) { - Bitmap screenShot = null; - try { - IAccessibilityServiceConnection connection = getConnection(connectionId); - if (connection != null) { - screenShot = connection.takeScreenshot(displayId); - } else { - if (DEBUG) { - Log.w(LOG_TAG, "No connection for connection id: " + connectionId); - } - } - } catch (RemoteException re) { - Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re); - } - return screenShot; - } - - /** * Clears the result state. */ private void clearResultLocked() { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 184f3302ae8d..05cf3edb4525 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -551,6 +551,14 @@ public class AccessibilityNodeInfo implements Parcelable { public static final String ACTION_ARGUMENT_PRESS_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_HOLD_DURATION_MILLIS_INT"; + /** + * Argument to represent the IME action Id to press the returning key on a node. + * For use with R.id.accessibilityActionImeEnter + * @hide + */ + public static final String ACTION_ARGUMENT_IME_ACTION_ID_INT = + "android.view.accessibility.action.ARGUMENT_IME_ACTION_ID_INT"; + // Focus types /** @@ -1644,8 +1652,12 @@ public class AccessibilityNodeInfo implements Parcelable { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + Bundle arguments = null; + if (mExtras != null) { + arguments = mExtras; + } return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, - action, null); + action, arguments); } /** @@ -4210,6 +4222,8 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_HIDE_TOOLTIP"; case R.id.accessibilityActionPressAndHold: return "ACTION_PRESS_AND_HOLD"; + case R.id.accessibilityActionImeEnter: + return "ACTION_IME_ENTER"; default: return "ACTION_UNKNOWN"; } @@ -4839,6 +4853,13 @@ public class AccessibilityNodeInfo implements Parcelable { @NonNull public static final AccessibilityAction ACTION_PRESS_AND_HOLD = new AccessibilityAction(R.id.accessibilityActionPressAndHold); + /** + * Action to send ime action. A node should expose this action only for views that are + * currently with input focus and editable. + */ + @NonNull public static final AccessibilityAction ACTION_IME_ENTER = + new AccessibilityAction(R.id.accessibilityActionImeEnter); + private final int mActionId; private final CharSequence mLabel; diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 80027b1ed75d..6246b507ba59 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -323,6 +323,7 @@ public final class ConversationActions implements Parcelable { private int mUserId = UserHandle.USER_NULL; @NonNull private Bundle mExtras; + private boolean mUseDefaultTextClassifier; private Request( @NonNull List<Message> conversation, @@ -347,6 +348,8 @@ public final class ConversationActions implements Parcelable { String callingPackageName = in.readString(); int userId = in.readInt(); Bundle extras = in.readBundle(); + boolean useDefaultTextClassifier = in.readBoolean(); + Request request = new Request( conversation, typeConfig, @@ -355,6 +358,7 @@ public final class ConversationActions implements Parcelable { extras); request.setCallingPackageName(callingPackageName); request.setUserId(userId); + request.setUseDefaultTextClassifier(useDefaultTextClassifier); return request; } @@ -367,6 +371,7 @@ public final class ConversationActions implements Parcelable { parcel.writeString(mCallingPackageName); parcel.writeInt(mUserId); parcel.writeBundle(mExtras); + parcel.writeBoolean(mUseDefaultTextClassifier); } @Override @@ -455,6 +460,26 @@ public final class ConversationActions implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the extended data related to this request. * * <p><b>NOTE: </b>Do not modify this bundle. diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 09cb7a07faa8..e0f29a981d89 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -140,6 +140,7 @@ public final class SelectionEvent implements Parcelable { private int mEnd; private int mSmartStart; private int mSmartEnd; + private boolean mUseDefaultTextClassifier; SelectionEvent( int start, int end, @@ -175,6 +176,7 @@ public final class SelectionEvent implements Parcelable { mSmartStart = in.readInt(); mSmartEnd = in.readInt(); mUserId = in.readInt(); + mUseDefaultTextClassifier = in.readBoolean(); } @Override @@ -204,6 +206,7 @@ public final class SelectionEvent implements Parcelable { dest.writeInt(mSmartStart); dest.writeInt(mSmartEnd); dest.writeInt(mUserId); + dest.writeBoolean(mUseDefaultTextClassifier); } @Override @@ -428,6 +431,26 @@ public final class SelectionEvent implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the type of widget that was involved in triggering this event. */ @WidgetType @@ -642,7 +665,8 @@ public final class SelectionEvent implements Parcelable { return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, - mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, + mUseDefaultTextClassifier); } @Override @@ -673,7 +697,8 @@ public final class SelectionEvent implements Parcelable { && mStart == other.mStart && mEnd == other.mEnd && mSmartStart == other.mSmartStart - && mSmartEnd == other.mSmartEnd; + && mSmartEnd == other.mSmartEnd + && mUseDefaultTextClassifier == other.mUseDefaultTextClassifier; } @Override @@ -683,12 +708,13 @@ public final class SelectionEvent implements Parcelable { + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, " + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " + "durationSincePreviousEvent=%d, eventIndex=%d," - + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", + + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d, " + + "mUseDefaultTextClassifier=%b}", mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mUserId, mResultId, mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, mEventIndex, - mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); + mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mUseDefaultTextClassifier); } public static final @android.annotation.NonNull Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() { diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index 138d25dd7c83..fe5e8d658dc3 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -55,17 +55,20 @@ public final class SystemTextClassifier implements TextClassifier { // service will throw a remote exception. @UserIdInt private final int mUserId; + private final boolean mUseDefault; private TextClassificationSessionId mSessionId; - public SystemTextClassifier(Context context, TextClassificationConstants settings) - throws ServiceManager.ServiceNotFoundException { + public SystemTextClassifier( + Context context, + TextClassificationConstants settings, + boolean useDefault) throws ServiceManager.ServiceNotFoundException { mManagerService = ITextClassifierService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); mSettings = Objects.requireNonNull(settings); - mFallback = context.getSystemService(TextClassificationManager.class) - .getTextClassifier(TextClassifier.LOCAL); + mFallback = TextClassifier.NO_OP; mPackageName = Objects.requireNonNull(context.getOpPackageName()); mUserId = context.getUserId(); + mUseDefault = useDefault; } /** @@ -79,6 +82,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); request.setUserId(mUserId); + request.setUseDefaultTextClassifier(mUseDefault); final BlockingCallback<TextSelection> callback = new BlockingCallback<>("textselection"); mManagerService.onSuggestSelection(mSessionId, request, callback); @@ -103,6 +107,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); request.setUserId(mUserId); + request.setUseDefaultTextClassifier(mUseDefault); final BlockingCallback<TextClassification> callback = new BlockingCallback<>("textclassification"); mManagerService.onClassifyText(mSessionId, request, callback); @@ -124,7 +129,9 @@ public final class SystemTextClassifier implements TextClassifier { public TextLinks generateLinks(@NonNull TextLinks.Request request) { Objects.requireNonNull(request); Utils.checkMainThread(); - + if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) { + return mFallback.generateLinks(request); + } if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) { return Utils.generateLegacyLinks(request); } @@ -132,6 +139,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); request.setUserId(mUserId); + request.setUseDefaultTextClassifier(mUseDefault); final BlockingCallback<TextLinks> callback = new BlockingCallback<>("textlinks"); mManagerService.onGenerateLinks(mSessionId, request, callback); @@ -152,6 +160,7 @@ public final class SystemTextClassifier implements TextClassifier { try { event.setUserId(mUserId); + event.setUseDefaultTextClassifier(mUseDefault); mManagerService.onSelectionEvent(mSessionId, event); } catch (RemoteException e) { Log.e(LOG_TAG, "Error reporting selection event.", e); @@ -169,6 +178,7 @@ public final class SystemTextClassifier implements TextClassifier { .build() : event.getEventContext(); tcContext.setUserId(mUserId); + tcContext.setUseDefaultTextClassifier(mUseDefault); event.setEventContext(tcContext); mManagerService.onTextClassifierEvent(mSessionId, event); } catch (RemoteException e) { @@ -184,6 +194,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); request.setUserId(mUserId); + request.setUseDefaultTextClassifier(mUseDefault); final BlockingCallback<TextLanguage> callback = new BlockingCallback<>("textlanguage"); mManagerService.onDetectLanguage(mSessionId, request, callback); @@ -205,6 +216,7 @@ public final class SystemTextClassifier implements TextClassifier { try { request.setCallingPackageName(mPackageName); request.setUserId(mUserId); + request.setUseDefaultTextClassifier(mUseDefault); final BlockingCallback<ConversationActions> callback = new BlockingCallback<>("conversation-actions"); mManagerService.onSuggestConversationActions(mSessionId, request, callback); @@ -225,7 +237,7 @@ public final class SystemTextClassifier implements TextClassifier { @WorkerThread public int getMaxGenerateLinksTextLength() { // TODO: retrieve this from the bound service. - return mFallback.getMaxGenerateLinksTextLength(); + return mSettings.getGenerateLinksMaxTextLength(); } @Override @@ -247,6 +259,7 @@ public final class SystemTextClassifier implements TextClassifier { printWriter.printPair("mPackageName", mPackageName); printWriter.printPair("mSessionId", mSessionId); printWriter.printPair("mUserId", mUserId); + printWriter.printPair("mUseDefault", mUseDefault); printWriter.decreaseIndent(); printWriter.println(); } diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 3628d2d40c1e..00f762b44a07 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -555,6 +555,7 @@ public final class TextClassification implements Parcelable { @Nullable private String mCallingPackageName; @UserIdInt private int mUserId = UserHandle.USER_NULL; + private boolean mUseDefaultTextClassifier; private Request( CharSequence text, @@ -654,6 +655,26 @@ public final class TextClassification implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Do not modify this bundle. @@ -755,6 +776,7 @@ public final class TextClassification implements Parcelable { dest.writeString(mCallingPackageName); dest.writeInt(mUserId); dest.writeBundle(mExtras); + dest.writeBoolean(mUseDefaultTextClassifier); } private static Request readFromParcel(Parcel in) { @@ -768,11 +790,13 @@ public final class TextClassification implements Parcelable { final String callingPackageName = in.readString(); final int userId = in.readInt(); final Bundle extras = in.readBundle(); + final boolean useDefaultTextClassifier = in.readBoolean(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, referenceTime, extras); request.setCallingPackageName(callingPackageName); request.setUserId(userId); + request.setUseDefaultTextClassifier(useDefaultTextClassifier); return request; } diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index ed69513b7f69..3d5ac58e7704 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -17,6 +17,7 @@ package android.view.textclassifier; import android.annotation.Nullable; +import android.content.Context; import android.provider.DeviceConfig; import com.android.internal.annotations.VisibleForTesting; @@ -167,6 +168,16 @@ public final class TextClassificationConstants { static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = "textclassifier_service_package_override"; + /** + * Whether to use the default system text classifier as the default text classifier + * implementation. The local text classifier is used if it is {@code false}. + * + * @see android.service.textclassifier.TextClassifierService#getDefaultTextClassifierImplementation(Context) + */ + // TODO: Once the system health experiment is done, remove this together with local TC. + private static final String USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL = + "use_default_system_text_classifier_as_default_impl"; + private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -209,7 +220,8 @@ public final class TextClassificationConstants { private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true; private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true; private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true; - private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f}; + private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[]{20f, 1.0f, 0.4f}; + private static final boolean USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT = true; @Nullable public String getTextClassifierServicePackageOverride() { @@ -331,6 +343,13 @@ public final class TextClassificationConstants { LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT); } + public boolean getUseDefaultTextClassifierAsDefaultImplementation() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL, + USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT); + } + void dump(IndentingPrintWriter pw) { pw.println("TextClassificationConstants:"); pw.increaseIndent(); @@ -378,6 +397,8 @@ public final class TextClassificationConstants { .println(); pw.printPair("textclassifier_service_package_override", getTextClassifierServicePackageOverride()).println(); + pw.printPair("use_default_system_text_classifier_as_default_impl", + getUseDefaultTextClassifierAsDefaultImplementation()).println(); pw.decreaseIndent(); } diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java index 930765b29197..d58d175c9c93 100644 --- a/core/java/android/view/textclassifier/TextClassificationContext.java +++ b/core/java/android/view/textclassifier/TextClassificationContext.java @@ -38,6 +38,7 @@ public final class TextClassificationContext implements Parcelable { @Nullable private final String mWidgetVersion; @UserIdInt private int mUserId = UserHandle.USER_NULL; + private boolean mUseDefaultTextClassifier; private TextClassificationContext( String packageName, @@ -76,6 +77,26 @@ public final class TextClassificationContext implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the widget type for this classification context. */ @NonNull @@ -156,6 +177,7 @@ public final class TextClassificationContext implements Parcelable { parcel.writeString(mWidgetType); parcel.writeString(mWidgetVersion); parcel.writeInt(mUserId); + parcel.writeBoolean(mUseDefaultTextClassifier); } private TextClassificationContext(Parcel in) { @@ -163,6 +185,7 @@ public final class TextClassificationContext implements Parcelable { mWidgetType = in.readString(); mWidgetVersion = in.readString(); mUserId = in.readInt(); + mUseDefaultTextClassifier = in.readBoolean(); } public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR = diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index bb96d5543b0b..dfbec9b67027 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -25,7 +25,6 @@ import android.content.Context; import android.os.ServiceManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; -import android.service.textclassifier.TextClassifierService; import android.view.textclassifier.TextClassifier.TextClassifierType; import com.android.internal.annotations.GuardedBy; @@ -62,9 +61,6 @@ public final class TextClassificationManager { @Nullable private TextClassifier mLocalTextClassifier; @GuardedBy("mLock") - @Nullable - private TextClassifier mSystemTextClassifier; - @GuardedBy("mLock") private TextClassificationSessionFactory mSessionFactory; @GuardedBy("mLock") private TextClassificationConstants mSettings; @@ -91,8 +87,8 @@ public final class TextClassificationManager { synchronized (mLock) { if (mCustomTextClassifier != null) { return mCustomTextClassifier; - } else if (isSystemTextClassifierEnabled()) { - return getSystemTextClassifier(); + } else if (getSettings().isSystemTextClassifierEnabled()) { + return getSystemTextClassifier(SystemTextClassifier.SYSTEM); } else { return getLocalTextClassifier(); } @@ -116,6 +112,7 @@ public final class TextClassificationManager { * * @see TextClassifier#LOCAL * @see TextClassifier#SYSTEM + * @see TextClassifier#DEFAULT_SERVICE * @hide */ @UnsupportedAppUsage @@ -124,7 +121,7 @@ public final class TextClassificationManager { case TextClassifier.LOCAL: return getLocalTextClassifier(); default: - return getSystemTextClassifier(); + return getSystemTextClassifier(type); } } @@ -204,21 +201,22 @@ public final class TextClassificationManager { } } - private TextClassifier getSystemTextClassifier() { + /** @hide */ + private TextClassifier getSystemTextClassifier(@TextClassifierType int type) { synchronized (mLock) { - if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { + if (getSettings().isSystemTextClassifierEnabled()) { try { - mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings()); - Log.d(LOG_TAG, "Initialized SystemTextClassifier"); + Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = " + type); + return new SystemTextClassifier( + mContext, + getSettings(), + /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE); } catch (ServiceManager.ServiceNotFoundException e) { Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); } } + return TextClassifier.NO_OP; } - if (mSystemTextClassifier != null) { - return mSystemTextClassifier; - } - return TextClassifier.NO_OP; } /** @@ -240,11 +238,6 @@ public final class TextClassificationManager { } } - private boolean isSystemTextClassifierEnabled() { - return getSettings().isSystemTextClassifierEnabled() - && TextClassifierService.getServiceComponentName(mContext) != null; - } - /** @hide */ @VisibleForTesting public void invalidateForTesting() { @@ -261,7 +254,6 @@ public final class TextClassificationManager { private void invalidateTextClassifiers() { synchronized (mLock) { mLocalTextClassifier = null; - mSystemTextClassifier = null; } } @@ -274,7 +266,8 @@ public final class TextClassificationManager { /** @hide **/ public void dump(IndentingPrintWriter pw) { getLocalTextClassifier().dump(pw); - getSystemTextClassifier().dump(pw); + getSystemTextClassifier(TextClassifier.DEFAULT_SERVICE).dump(pw); + getSystemTextClassifier(TextClassifier.SYSTEM).dump(pw); getSettings().dump(pw); } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 9b3369390b6a..2cc226d5a601 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -66,12 +66,14 @@ public interface TextClassifier { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {LOCAL, SYSTEM}) + @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SERVICE}) @interface TextClassifierType {} // TODO: Expose as system APIs. /** Specifies a TextClassifier that runs locally in the app's process. @hide */ int LOCAL = 0; /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */ int SYSTEM = 1; + /** Specifies the default TextClassifier that runs in the system process. @hide */ + int DEFAULT_SERVICE = 2; /** The TextClassifier failed to run. */ String TYPE_UNKNOWN = ""; @@ -667,8 +669,10 @@ public interface TextClassifier { Preconditions.checkArgument(endIndex > startIndex); } - static void checkTextLength(CharSequence text, int maxLength) { - Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()"); + /** Returns if the length of the text is within the range. */ + static boolean checkTextLength(CharSequence text, int maxLength) { + int textLength = text.length(); + return textLength >= 0 && textLength <= maxLength; } /** diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 61bd7c72b80b..d7149ee05b57 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -286,8 +286,10 @@ public final class TextClassifierImpl implements TextClassifier { @WorkerThread public TextLinks generateLinks(@NonNull TextLinks.Request request) { Objects.requireNonNull(request); - Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength()); Utils.checkMainThread(); + if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) { + return mFallback.generateLinks(request); + } if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) { return Utils.generateLegacyLinks(request); diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index cc9109e6e9bb..58024dcc09b9 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -230,6 +230,7 @@ public final class TextLanguage implements Parcelable { @Nullable private String mCallingPackageName; @UserIdInt private int mUserId = UserHandle.USER_NULL; + private boolean mUseDefaultTextClassifier; private Request(CharSequence text, Bundle bundle) { mText = text; @@ -283,6 +284,26 @@ public final class TextLanguage implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns a bundle containing non-structured extra information about this request. * * <p><b>NOTE: </b>Do not modify this bundle. @@ -303,6 +324,7 @@ public final class TextLanguage implements Parcelable { dest.writeString(mCallingPackageName); dest.writeInt(mUserId); dest.writeBundle(mExtra); + dest.writeBoolean(mUseDefaultTextClassifier); } private static Request readFromParcel(Parcel in) { @@ -310,10 +332,12 @@ public final class TextLanguage implements Parcelable { final String callingPackageName = in.readString(); final int userId = in.readInt(); final Bundle extra = in.readBundle(); + final boolean useDefaultTextClassifier = in.readBoolean(); final Request request = new Request(text, extra); request.setCallingPackageName(callingPackageName); request.setUserId(userId); + request.setUseDefaultTextClassifier(useDefaultTextClassifier); return request; } diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index bda12b0893d1..7430cb38b987 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -345,6 +345,7 @@ public final class TextLinks implements Parcelable { @Nullable private final ZonedDateTime mReferenceTime; @UserIdInt private int mUserId = UserHandle.USER_NULL; + private boolean mUseDefaultTextClassifier; private Request( CharSequence text, @@ -447,6 +448,26 @@ public final class TextLinks implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Do not modify this bundle. @@ -568,6 +589,7 @@ public final class TextLinks implements Parcelable { dest.writeInt(mUserId); dest.writeBundle(mExtras); dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); + dest.writeBoolean(mUseDefaultTextClassifier); } private static Request readFromParcel(Parcel in) { @@ -580,11 +602,13 @@ public final class TextLinks implements Parcelable { final String referenceTimeString = in.readString(); final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); + final boolean useDefaultTextClassifier = in.readBoolean(); final Request request = new Request(text, defaultLocales, entityConfig, /* legacyFallback= */ true, referenceTime, extras); request.setCallingPackageName(callingPackageName); request.setUserId(userId); + request.setUseDefaultTextClassifier(useDefaultTextClassifier); return request; } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 4a36cbfc35e5..575a072d7066 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -216,6 +216,7 @@ public final class TextSelection implements Parcelable { @Nullable private String mCallingPackageName; @UserIdInt private int mUserId = UserHandle.USER_NULL; + private boolean mUseDefaultTextClassifier; private Request( CharSequence text, @@ -316,6 +317,26 @@ public final class TextSelection implements Parcelable { } /** + * Sets whether to use the default text classifier to handle this request. + * This will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) { + mUseDefaultTextClassifier = useDefaultTextClassifier; + } + + /** + * Returns whether to use the default text classifier to handle this request. This + * will be ignored if it is not the system text classifier to handle this request. + * + * @hide + */ + public boolean getUseDefaultTextClassifier() { + return mUseDefaultTextClassifier; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Do not modify this bundle. @@ -420,6 +441,7 @@ public final class TextSelection implements Parcelable { dest.writeString(mCallingPackageName); dest.writeInt(mUserId); dest.writeBundle(mExtras); + dest.writeBoolean(mUseDefaultTextClassifier); } private static Request readFromParcel(Parcel in) { @@ -430,11 +452,13 @@ public final class TextSelection implements Parcelable { final String callingPackageName = in.readString(); final int userId = in.readInt(); final Bundle extras = in.readBundle(); + final boolean systemTextClassifierType = in.readBoolean(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, /* darkLaunchAllowed= */ false, extras); request.setCallingPackageName(callingPackageName); request.setUserId(userId); + request.setUseDefaultTextClassifier(systemTextClassifierType); return request; } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 4752eadfe385..9f03d956f22c 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -756,9 +756,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ private ListItemAccessibilityDelegate mAccessibilityDelegate; - private int mLastAccessibilityScrollEventFromIndex; - private int mLastAccessibilityScrollEventToIndex; - /** * Track the item count from the last time we handled a data change. */ @@ -1520,25 +1517,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. } - /** @hide */ - @Override - public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { - // Since this class calls onScrollChanged even if the mFirstPosition and the - // child count have not changed we will avoid sending duplicate accessibility - // events. - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - final int firstVisiblePosition = getFirstVisiblePosition(); - final int lastVisiblePosition = getLastVisiblePosition(); - if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition - && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { - return; - } else { - mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; - mLastAccessibilityScrollEventToIndex = lastVisiblePosition; - } - } - super.sendAccessibilityEventUnchecked(event); - } + /** + * A TYPE_VIEW_SCROLLED event should be sent whenever a scroll happens, even if the + * mFirstPosition and the child count have not changed. + */ @Override public CharSequence getAccessibilityClassName() { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 7fd41508dfc3..4e4a9837b32c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -454,15 +454,17 @@ public class Editor { aspectRatio = 5.5f; } - final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); - final float sourceHeight = fontMetrics.descent - fontMetrics.ascent; + final Layout layout = mTextView.getLayout(); + final int line = layout.getLineForOffset(mTextView.getSelectionStart()); + final int sourceHeight = + layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line); // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true. int height = (int)(sourceHeight * zoom) + 2; int width = (int)(aspectRatio * height); params.setFishEyeStyle() .setSize(width, height) - .setSourceSize(width, Math.round(sourceHeight)) + .setSourceSize(width, sourceHeight) .setElevation(0) .setInitialZoom(zoom) .setClippingEnabled(false); @@ -5041,7 +5043,7 @@ public class Editor { // Vertically snap to middle of current line. showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber) - + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f + + mTextView.getLayout().getLineBottomWithoutSpacing(lineNumber)) / 2.0f + mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY; return true; } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 79ec680f4c52..3c3daa3c7001 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -3255,6 +3255,9 @@ public class ListView extends AbsListView { */ @UnsupportedAppUsage private void scrollListItemsBy(int amount) { + int oldX = mScrollX; + int oldY = mScrollY; + offsetChildrenTopAndBottom(amount); final int listBottom = getHeight() - mListPadding.bottom; @@ -3327,6 +3330,7 @@ public class ListView extends AbsListView { recycleBin.fullyDetachScrapViews(); removeUnusedFixedViews(mHeaderViewInfos); removeUnusedFixedViews(mFooterViewInfos); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); } private View addViewAbove(View theView, int position) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8ce6ee576b00..cbfa05caac6e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -422,6 +422,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ static final int PROCESS_TEXT_REQUEST_CODE = 100; + // Accessibility action to send IME custom action for CTS testing. + public static final int ACCESSIBILITY_ACTION_IME_ENTER = R.id.accessibilityActionImeEnter; + /** * Return code of {@link #doKeyDown}. */ @@ -11741,6 +11744,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.setContentInvalid(true); info.setError(mEditor.mError); } + // TextView will expose this action if it is editable and has focus. + if (isTextEditable() && isFocused()) { + CharSequence imeActionLabel = mContext.getResources().getString( + com.android.internal.R.string.keyboardview_keycode_enter); + if (getImeActionId() != 0 && getImeActionLabel() != null) { + imeActionLabel = getImeActionLabel(); + final int imeActionId = getImeActionId(); + // put ime action id into the extra data with ACTION_ARGUMENT_IME_ACTION_ID_INT. + final Bundle argument = info.getExtras(); + argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT, + imeActionId); + } + AccessibilityNodeInfo.AccessibilityAction action = + new AccessibilityNodeInfo.AccessibilityAction( + ACCESSIBILITY_ACTION_IME_ENTER, imeActionLabel); + info.addAction(action); + } } if (!TextUtils.isEmpty(mText)) { @@ -12052,6 +12072,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } return true; + case ACCESSIBILITY_ACTION_IME_ENTER: { + if (isFocused() && isTextEditable()) { + final int imeActionId = (arguments != null) ? arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT, + EditorInfo.IME_ACTION_UNSPECIFIED) + : EditorInfo.IME_ACTION_UNSPECIFIED; + if (imeActionId == getImeActionId()) { + onEditorAction(imeActionId); + } + } + } return true; default: { return super.performAccessibilityActionInternal(action, arguments); } diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 78d4e61ea953..a2c70b9afbee 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -148,6 +148,9 @@ public class Toast { @Nullable private CharSequence mText; + // TODO(b/144152069): Remove this after assessing impact on dogfood. + private boolean mIsCustomToast; + /** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. @@ -214,7 +217,8 @@ public class Toast { service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { - service.enqueueToast(pkg, mToken, tn, mDuration, displayId); + service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId, + mIsCustomToast); } } catch (RemoteException e) { // Empty @@ -252,12 +256,17 @@ public class Toast { */ @Deprecated public void setView(View view) { + mIsCustomToast = true; mNextView = view; } /** * Return the view. * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher that haven't called {@link + * #setView(View)} with a non-{@code null} view, this method will return {@code null}. + * * @see #setView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the * {@link #makeText(Context, CharSequence, int)} method, or use a @@ -266,6 +275,7 @@ public class Toast { * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background * will not have custom toast views displayed. */ + @Deprecated public View getView() { return mNextView; } @@ -292,6 +302,10 @@ public class Toast { /** * Set the margins of the view. * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when + * called on text toasts. + * * @param horizontalMargin The horizontal margin, in percentage of the * container width, between the container's edges and the * notification @@ -300,30 +314,59 @@ public class Toast { * notification */ public void setMargin(float horizontalMargin, float verticalMargin) { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "setMargin() shouldn't be called on text toasts, the values won't be used"); + } mTN.mHorizontalMargin = horizontalMargin; mTN.mVerticalMargin = verticalMargin; } /** * Return the horizontal margin. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public float getHorizontalMargin() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getHorizontalMargin() shouldn't be called on text toasts, the result may " + + "not reflect actual values."); + } return mTN.mHorizontalMargin; } /** * Return the vertical margin. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public float getVerticalMargin() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getVerticalMargin() shouldn't be called on text toasts, the result may not" + + " reflect actual values."); + } return mTN.mVerticalMargin; } /** * Set the location at which the notification should appear on the screen. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when + * called on text toasts. + * * @see android.view.Gravity * @see #getGravity */ public void setGravity(int gravity, int xOffset, int yOffset) { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "setGravity() shouldn't be called on text toasts, the values won't be used"); + } mTN.mGravity = gravity; mTN.mX = xOffset; mTN.mY = yOffset; @@ -331,27 +374,59 @@ public class Toast { /** * Get the location at which the notification should appear on the screen. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. + * * @see android.view.Gravity * @see #getGravity */ public int getGravity() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getGravity() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mGravity; } /** * Return the X offset in pixels to apply to the gravity's location. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public int getXOffset() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getXOffset() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mX; } /** * Return the Y offset in pixels to apply to the gravity's location. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public int getYOffset() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getYOffset() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mY; } + private boolean isSystemRenderedTextToast() { + return Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null; + } + /** * Adds a callback to be notified when the toast is shown or hidden. * @@ -385,7 +460,7 @@ public class Toast { } /** - * Make a standard toast that just contains a text view. + * Make a standard toast that just contains text. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. @@ -428,7 +503,7 @@ public class Toast { } /** - * Make a standard toast that just contains a text view with the text from a resource. + * Make a standard toast that just contains text from a resource. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index 6a6a60d6617c..82eb55a30a4e 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -267,8 +267,7 @@ public class AccessibilityButtonChooserActivity extends Activity { targets.addAll(getAccessibilityServiceTargets(context)); targets.addAll(getWhiteListingServiceTargets(context)); - final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); targets.removeIf(target -> !requiredTargets.contains(target.getId())); @@ -277,8 +276,7 @@ public class AccessibilityButtonChooserActivity extends Activity { private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets( @NonNull Context context) { - final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); final List<AccessibilityServiceInfo> installedServices = ams.getInstalledAccessibilityServiceList(); if (installedServices == null) { @@ -354,10 +352,11 @@ public class AccessibilityButtonChooserActivity extends Activity { } private static class ViewHolder { + View mItemView; ImageView mIconView; TextView mLabelView; FrameLayout mItemContainer; - ImageView mViewItem; + ImageView mActionViewItem; Switch mSwitchItem; } @@ -407,12 +406,13 @@ public class AccessibilityButtonChooserActivity extends Activity { R.layout.accessibility_button_chooser_item, parent, /* attachToRoot= */ false); holder = new ViewHolder(); + holder.mItemView = convertView; holder.mIconView = convertView.findViewById(R.id.accessibility_button_target_icon); holder.mLabelView = convertView.findViewById( R.id.accessibility_button_target_label); holder.mItemContainer = convertView.findViewById( R.id.accessibility_button_target_item_container); - holder.mViewItem = convertView.findViewById( + holder.mActionViewItem = convertView.findViewById( R.id.accessibility_button_target_view_item); holder.mSwitchItem = convertView.findViewById( R.id.accessibility_button_target_switch_item); @@ -465,11 +465,12 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mIconView.setAlpha(enabledState ? ENABLED_ALPHA : DISABLED_ALPHA); holder.mLabelView.setEnabled(enabledState); - holder.mViewItem.setEnabled(enabledState); - holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mViewItem.setVisibility(View.VISIBLE); + holder.mActionViewItem.setEnabled(enabledState); + holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); + holder.mActionViewItem.setVisibility(View.VISIBLE); holder.mSwitchItem.setVisibility(View.GONE); holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE); + holder.mItemView.setEnabled(enabledState); } private void updateInvisibleActionItemVisibility(@NonNull Context context, @@ -477,12 +478,13 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mIconView.setColorFilter(null); holder.mIconView.setAlpha(ENABLED_ALPHA); holder.mLabelView.setEnabled(true); - holder.mViewItem.setEnabled(true); - holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mViewItem.setVisibility(View.VISIBLE); + holder.mActionViewItem.setEnabled(true); + holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); + holder.mActionViewItem.setVisibility(View.VISIBLE); holder.mSwitchItem.setVisibility(View.GONE); holder.mItemContainer.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) ? View.VISIBLE : View.GONE); + holder.mItemView.setEnabled(true); } private void updateIntuitiveActionItemVisibility(@NonNull Context context, @@ -495,12 +497,13 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mIconView.setColorFilter(null); holder.mIconView.setAlpha(ENABLED_ALPHA); holder.mLabelView.setEnabled(true); - holder.mViewItem.setEnabled(true); - holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + holder.mActionViewItem.setEnabled(true); + holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); + holder.mActionViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE); holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled); holder.mItemContainer.setVisibility(View.VISIBLE); + holder.mItemView.setEnabled(true); } private void updateBounceActionItemVisibility(@NonNull Context context, @@ -508,12 +511,13 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mIconView.setColorFilter(null); holder.mIconView.setAlpha(ENABLED_ALPHA); holder.mLabelView.setEnabled(true); - holder.mViewItem.setEnabled(true); - holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); - holder.mViewItem.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) + holder.mActionViewItem.setEnabled(true); + holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); + holder.mActionViewItem.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) ? View.VISIBLE : View.GONE); holder.mSwitchItem.setVisibility(View.GONE); holder.mItemContainer.setVisibility(View.VISIBLE); + holder.mItemView.setEnabled(true); } } @@ -559,8 +563,7 @@ public class AccessibilityButtonChooserActivity extends Activity { private static boolean isAccessibilityServiceEnabled(@NonNull Context context, AccessibilityButtonTarget target) { - final AccessibilityManager ams = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); final List<AccessibilityServiceInfo> enabledServices = ams.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); @@ -598,8 +601,7 @@ public class AccessibilityButtonChooserActivity extends Activity { private void onLegacyTargetSelected(AccessibilityButtonTarget target) { if (mShortcutType == ACCESSIBILITY_BUTTON) { - final AccessibilityManager ams = (AccessibilityManager) getSystemService( - Context.ACCESSIBILITY_SERVICE); + final AccessibilityManager ams = getSystemService(AccessibilityManager.class); ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { switchServiceState(target); @@ -607,9 +609,12 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onInvisibleTargetSelected(AccessibilityButtonTarget target) { - final AccessibilityManager ams = (AccessibilityManager) getSystemService( - Context.ACCESSIBILITY_SERVICE); - ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); + final AccessibilityManager ams = getSystemService(AccessibilityManager.class); + if (mShortcutType == ACCESSIBILITY_BUTTON) { + ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId()); + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + ams.performAccessibilityShortcut(target.getId()); + } } private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) { diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java new file mode 100644 index 000000000000..fbdbbfb06b78 --- /dev/null +++ b/core/java/com/android/internal/app/BlockedAppActivity.java @@ -0,0 +1,86 @@ +/* + * 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.internal.app; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; + +/** + * A dialog shown to the user when they try to launch an app that is not allowed in lock task + * mode. The intent to start this activity must be created with the static factory method provided + * below. + */ +public class BlockedAppActivity extends AlertActivity { + + private static final String TAG = "BlockedAppActivity"; + private static final String PACKAGE_NAME = "com.android.internal.app"; + private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1); + if (userId < 0) { + Slog.wtf(TAG, "Invalid user: " + userId); + finish(); + return; + } + + String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE); + if (TextUtils.isEmpty(packageName)) { + Slog.wtf(TAG, "Invalid package: " + packageName); + finish(); + return; + } + + CharSequence appLabel = getAppLabel(userId, packageName); + + mAlertParams.mTitle = getString(R.string.app_blocked_title); + mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel); + mAlertParams.mPositiveButtonText = getString(android.R.string.ok); + setupAlert(); + } + + private CharSequence getAppLabel(int userId, String packageName) { + PackageManager pm = getPackageManager(); + try { + ApplicationInfo aInfo = + pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); + return aInfo.loadLabel(pm); + } catch (PackageManager.NameNotFoundException ne) { + Slog.e(TAG, "Package " + packageName + " not found", ne); + } + return packageName; + } + + + /** Creates an intent that launches {@link BlockedAppActivity}. */ + public static Intent createIntent(int userId, String packageName) { + return new Intent() + .setClassName("android", BlockedAppActivity.class.getName()) + .putExtra(Intent.EXTRA_USER_ID, userId) + .putExtra(EXTRA_BLOCKED_PACKAGE, packageName); + } +} diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index a43e4fe118a9..65cad834d5be 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2424,6 +2424,10 @@ public class ChooserActivity extends ResolverActivity implements offset += findViewById(R.id.content_preview_container).getHeight(); } + if (hasWorkProfile() && ENABLE_TABBED_VIEW) { + offset += findViewById(R.id.tabs).getHeight(); + } + int directShareHeight = 0; rowsToShow = Math.min(4, rowsToShow); for (int i = 0, childCount = recyclerView.getChildCount(); diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index f5a19fe73a86..6d2d7356a2e8 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -52,6 +52,7 @@ public class VpnConfig implements Parcelable { public static final String DIALOGS_PACKAGE = "com.android.vpndialogs"; + // TODO: Rename this to something that encompasses Settings-based Platform VPNs as well. public static final String LEGACY_VPN = "[Legacy VPN]"; public static Intent getIntentForConfirmation() { diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index c6ed624d73ac..518911e652f6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -323,7 +323,7 @@ public class RuntimeInit { result.append(System.getProperty("java.vm.version")); // such as 1.1.0 result.append(" (Linux; U; Android "); - String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" + String version = Build.VERSION.RELEASE_OR_CODENAME; // "1.0" or "3.4b5" result.append(version.length() > 0 ? version : "1.0"); // add the model for the release build diff --git a/core/jni/Android.bp b/core/jni/Android.bp index cec68df3c949..35eb0fc986d7 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -35,8 +35,13 @@ cc_library_shared { "android_animation_PropertyValuesHolder.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", + "android_os_Trace.cpp", + "android_text_AndroidCharacter.cpp", "android_util_EventLog.cpp", "android_util_Log.cpp", + "android_util_StringBlock.cpp", + "android_util_XmlBlock.cpp", + "android_view_RenderNodeAnimator.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp", ], @@ -114,14 +119,12 @@ cc_library_shared { "android_view_KeyEvent.cpp", "android_view_MotionEvent.cpp", "android_view_PointerIcon.cpp", - "android_view_RenderNodeAnimator.cpp", "android_view_Surface.cpp", "android_view_SurfaceControl.cpp", "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", "android_view_TextureView.cpp", "android_view_VelocityTracker.cpp", - "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", @@ -138,7 +141,6 @@ cc_library_shared { "android_os_SELinux.cpp", "android_os_SharedMemory.cpp", "android_os_storage_StorageManager.cpp", - "android_os_Trace.cpp", "android_os_UEventObserver.cpp", "android_os_VintfObject.cpp", "android_os_VintfRuntimeInfo.cpp", @@ -150,8 +152,6 @@ cc_library_shared { "android_util_Binder.cpp", "android_util_MemoryIntArray.cpp", "android_util_Process.cpp", - "android_util_StringBlock.cpp", - "android_util_XmlBlock.cpp", "android_util_jar_StrictJarFile.cpp", "android_media_AudioDeviceAddress.cpp", "android_media_AudioEffectDescriptor.cpp", @@ -208,6 +208,7 @@ cc_library_shared { static_libs: [ "libasync_safe", + "libbinderthreadstateutils", "libdmabufinfo", "libgif", "libseccomp_policy", @@ -311,11 +312,8 @@ cc_library_shared { srcs: [ "android_content_res_ApkAssets.cpp", "android_os_MessageQueue.cpp", - "android_os_Trace.cpp", "android_util_AssetManager.cpp", "android_util_FileObserver.cpp", - "android_util_StringBlock.cpp", - "android_util_XmlBlock.cpp", ], }, }, diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 481be241cc99..657336e6910a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -634,8 +634,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; - char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; - char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; @@ -885,88 +883,45 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p bool skip_compilation = ((strcmp(voldDecryptBuf, "trigger_restart_min_framework") == 0) || (strcmp(voldDecryptBuf, "1") == 0)); - // Extra options for boot.art/boot.oat image generation. - parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf, - "-Xms", "-Ximage-compiler-option"); - parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf, - "-Xmx", "-Ximage-compiler-option"); - if (skip_compilation) { - addOption("-Ximage-compiler-option"); - addOption("--compiler-filter=assume-verified"); - } else { - parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf, - "--compiler-filter=", "-Ximage-compiler-option"); - } - - // If there is a boot profile, it takes precedence over the image and preloaded classes. - if (hasFile("/system/etc/boot-image.prof")) { - addOption("-Ximage-compiler-option"); - addOption("--profile-file=/system/etc/boot-image.prof"); - addOption("-Ximage-compiler-option"); - addOption("--compiler-filter=speed-profile"); - } else { - ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n", - strerror(errno)); - return -1; - } - - - // If there is a dirty-image-objects file, push it. - if (hasFile("/system/etc/dirty-image-objects")) { - addOption("-Ximage-compiler-option"); - addOption("--dirty-image-objects=/system/etc/dirty-image-objects"); - } - - property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, ""); - parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option"); - - // Extra options for DexClassLoader. - parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf, - "-Xms", "-Xcompiler-option"); - parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf, - "-Xmx", "-Xcompiler-option"); + // Extra options for JIT. if (skip_compilation) { addOption("-Xcompiler-option"); addOption("--compiler-filter=assume-verified"); - - // We skip compilation when a minimal runtime is brought up for decryption. In that case - // /data is temporarily backed by a tmpfs, which is usually small. - // If the system image contains prebuilts, they will be relocated into the tmpfs. In this - // specific situation it is acceptable to *not* relocate and run out of the prebuilts - // directly instead. - addOption("--runtime-arg"); - addOption("-Xnorelocate"); } else { parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf, "--compiler-filter=", "-Xcompiler-option"); } parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option"); - parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", - "-Ximage-compiler-option"); parseCompilerOption("dalvik.vm.dex2oat-cpu-set", dex2oatCpuSetBuf, "--cpu-set=", "-Xcompiler-option"); - parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=", - "-Ximage-compiler-option"); - - // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to - // pass the instruction-set-features/variant as an image-compiler-option. - // Note: it is OK to reuse the buffer, as the values are exactly the same between - // * compiler-option, used for runtime compilation (DexClassLoader) - // * image-compiler-option, used for boot-image compilation on device // Copy the variant. sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", ABI_STRING); parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, - "--instruction-set-variant=", "-Ximage-compiler-option"); - parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, "--instruction-set-variant=", "-Xcompiler-option"); // Copy the features. sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", ABI_STRING); parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, - "--instruction-set-features=", "-Ximage-compiler-option"); - parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, "--instruction-set-features=", "-Xcompiler-option"); + /* + * When running with debug.generate-debug-info, add --generate-debug-info to + * the compiler options so that both JITted code and the boot image extension, + * if it is compiled on device, will include native debugging information. + */ + property_get("debug.generate-debug-info", propBuf, ""); + bool generate_debug_info = (strcmp(propBuf, "true") == 0); + if (generate_debug_info) { + addOption("-Xcompiler-option"); + addOption("--generate-debug-info"); + } + + // The mini-debug-info makes it possible to backtrace through compiled code. + bool generate_mini_debug_info = property_get_bool("dalvik.vm.minidebuginfo", 0); + if (generate_mini_debug_info) { + addOption("-Xcompiler-option"); + addOption("--generate-mini-debug-info"); + } property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); @@ -975,6 +930,53 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p property_get("dalvik.vm.extra-opts", extraOptsBuf, ""); parseExtraOpts(extraOptsBuf, NULL); + // Extra options for boot image extension generation. + if (skip_compilation) { + addOption("-Xnoimage-dex2oat"); + } else { + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf, + "-Xms", "-Ximage-compiler-option"); + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf, + "-Xmx", "-Ximage-compiler-option"); + + parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf, + "--compiler-filter=", "-Ximage-compiler-option"); + + // If there is a dirty-image-objects file, push it. + if (hasFile("/system/etc/dirty-image-objects")) { + addOption("-Ximage-compiler-option"); + addOption("--dirty-image-objects=/system/etc/dirty-image-objects"); + } + + parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", + "-Ximage-compiler-option"); + parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=", + "-Ximage-compiler-option"); + + // The runtime may compile a boot image extension, when necessary, not using installd. + // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option. + // Note: it is OK to reuse the buffer, as the values are exactly the same between + // * compiler-option, used for runtime compilation (DexClassLoader) + // * image-compiler-option, used for boot-image compilation on device + parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, + "--instruction-set-variant=", "-Ximage-compiler-option"); + parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, + "--instruction-set-features=", "-Ximage-compiler-option"); + + if (generate_debug_info) { + addOption("-Ximage-compiler-option"); + addOption("--generate-debug-info"); + } + + if (generate_mini_debug_info) { + addOption("-Ximage-compiler-option"); + addOption("--generate-mini-debug-info"); + } + + property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, ""); + parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option"); + } + /* Set the properties for locale */ { strcpy(localeOption, "-Duser.locale="); @@ -1032,25 +1034,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf, "-Xzygote-max-boot-retry="); - /* - * When running with debug.generate-debug-info, add --generate-debug-info to - * the compiler options so that the boot image, if it is compiled on device, - * will include native debugging information. - */ - property_get("debug.generate-debug-info", propBuf, ""); - if (strcmp(propBuf, "true") == 0) { - addOption("-Xcompiler-option"); - addOption("--generate-debug-info"); - addOption("-Ximage-compiler-option"); - addOption("--generate-debug-info"); - } - - // The mini-debug-info makes it possible to backtrace through JIT code. - if (property_get_bool("dalvik.vm.minidebuginfo", 0)) { - addOption("-Xcompiler-option"); - addOption("--generate-mini-debug-info"); - } - // If set, the property below can be used to enable core platform API violation reporting. property_get("persist.debug.dalvik.vm.core_platform_api_policy", propBuf, ""); if (propBuf[0] != '\0') { diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 6c0680fe6707..571a33879411 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -75,10 +75,12 @@ extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv* env); extern int register_android_os_Trace(JNIEnv* env); +extern int register_android_text_AndroidCharacter(JNIEnv* env); extern int register_android_util_EventLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); +extern int register_android_view_RenderNodeAnimator(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env); @@ -90,58 +92,66 @@ struct RegJNIRec { // Map of all possible class names to register to their corresponding JNI registration function pointer // The actual list of registered classes will be determined at runtime via the 'native_classes' System property -static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { - {"android.animation.PropertyValuesHolder", REG_JNI(register_android_animation_PropertyValuesHolder)}, +static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { + {"android.animation.PropertyValuesHolder", + REG_JNI(register_android_animation_PropertyValuesHolder)}, #ifdef __linux__ - {"android.content.AssetManager", REG_JNI(register_android_content_AssetManager)}, - {"android.content.StringBlock", REG_JNI(register_android_content_StringBlock)}, - {"android.content.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, - {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, + {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, + {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)}, #endif - {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, - {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, - {"android.graphics.ByteBufferStreamAdaptor", REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, - {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, - {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)}, - {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, - {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, - {"android.graphics.CreateJavaOutputStreamAdaptor", REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, - {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, - {"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.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)}, - {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, - {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, - {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, - {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, - {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, - {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, - {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, - {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, - {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, - {"android.graphics.drawable.AnimatedVectorDrawable", REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, - {"android.graphics.drawable.VectorDrawable", REG_JNI(register_android_graphics_drawable_VectorDrawable)}, - {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)}, - {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)}, - {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)}, - {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)}, + {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, + {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, + {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, + {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, + {"android.graphics.ByteBufferStreamAdaptor", + REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, + {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, + {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)}, + {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, + {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, + {"android.graphics.CreateJavaOutputStreamAdaptor", + REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, + {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, + {"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.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)}, + {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, + {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, + {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, + {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, + {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, + {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, + {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, + {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, + {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, + {"android.graphics.drawable.AnimatedVectorDrawable", + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, + {"android.graphics.drawable.VectorDrawable", + REG_JNI(register_android_graphics_drawable_VectorDrawable)}, + {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)}, + {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)}, + {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)}, + {"android.graphics.text.MeasuredText", + REG_JNI(register_android_graphics_text_MeasuredText)}, #ifdef __linux__ - {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, - {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, + {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, + {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, #endif - {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, - {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, -#ifdef __linux__ - {"android.os.Trace", REG_JNI(register_android_os_Trace)}, -#endif - {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, - {"android.util.Log", REG_JNI(register_android_util_Log)}, - {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, - {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, - {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)}, + {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, + {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, + {"android.os.Trace", REG_JNI(register_android_os_Trace)}, + {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)}, + {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, + {"android.util.Log", REG_JNI(register_android_util_Log)}, + {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, + {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)}, + {"com.android.internal.util.VirtualRefBasePtr", + REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, + {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", + REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)}, }; // Vector to store the names of classes that need delegates of their native methods static vector<string> classesToDelegate; @@ -159,7 +169,6 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - string classNameString = string(className); if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString) != classesToDelegate.end()) { diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index aa209cb3899e..38fb8bdc1f7a 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -569,8 +569,8 @@ bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap) { // mRecycledBitmap specifies the width and height of the bitmap that we // want to reuse. Neither can be changed. We will try to find a way // to reuse the memory. - const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width()); - const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height()); + const int maxWidth = std::max(bitmap->width(), mRecycledBitmap->info().width()); + const int maxHeight = std::max(bitmap->height(), mRecycledBitmap->info().height()); const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight); const size_t rowBytes = maxInfo.minRowBytes(); const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes); diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index c269d1cf4295..14d74878b2ea 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -37,6 +37,7 @@ #include <binder/Parcel.h> #include <binder/ProcessState.h> #include <binder/Stability.h> +#include <binderthreadstate/CallerUtils.h> #include <cutils/atomic.h> #include <log/log.h> #include <utils/KeyedVector.h> @@ -950,7 +951,7 @@ static jint android_os_Binder_getCallingUid() static jboolean android_os_Binder_isHandlingTransaction() { - return IPCThreadState::self()->isServingCall(); + return getCurrentServingCall() == BinderCallType::BINDER; } static jlong android_os_Binder_clearCallingIdentity() diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 69ca17c08257..5a8225c1f21a 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -147,10 +147,12 @@ static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, job } static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz, - jboolean translucent, jlong rootRenderNodePtr) { + jboolean translucent, jboolean isWideGamut, jlong rootRenderNodePtr) { RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr); ContextFactoryImpl factory(rootRenderNode); - return (jlong) new RenderProxy(translucent, rootRenderNode, &factory); + RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory); + proxy->setWideGamut(isWideGamut); + return (jlong) proxy; } static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz, @@ -627,7 +629,7 @@ static const JNINativeMethod gMethods[] = { { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, - { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, + { "nCreateProxy", "(ZZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties }, { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName }, diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index a6e08d2cecc1..fc0a2ef6041a 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2562,7 +2562,7 @@ enum PageId { // CATEGORY: SETTINGS // OS: R OPEN_SUPPORTED_LINKS = 1824; - + // OPEN: Settings > Display > Dark theme > Set start time dialog DIALOG_DARK_THEME_SET_START_TIME = 1825; @@ -2573,4 +2573,9 @@ enum PageId { // CATEGORY: SETTINGS // OS: R VIBRATE_FOR_CALLS = 1827; + + // OPEN: Settings > Connected devices > Connection preferences > NFC + // CATEGORY: SETTINGS + // OS: R + CONNECTION_DEVICE_ADVANCED_NFC = 1828; } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 8adcc9ed905d..bf4cdee72b2d 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -52,6 +52,7 @@ import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; import "frameworks/base/core/proto/android/service/restricted_image.proto"; +import "frameworks/base/core/proto/android/service/sensor_service.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/util/event_log_tags.proto"; import "frameworks/base/core/proto/android/util/log.proto"; @@ -492,6 +493,11 @@ message IncidentProto { (section).args = "contexthub --proto" ]; + optional android.service.SensorServiceProto sensor_service = 3053 [ + (section).type = SECTION_DUMPSYS, + (section).args = "sensorservice --proto" + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 0a2fd7093a49..2d2ead455a4d 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -27,7 +27,6 @@ import "frameworks/base/core/proto/android/content/component_name.proto"; import "frameworks/base/core/proto/android/content/configuration.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; import "frameworks/base/core/proto/android/content/package_item_info.proto"; -import "frameworks/base/core/proto/android/graphics/rect.proto"; import "frameworks/base/core/proto/android/internal/processstats.proto"; import "frameworks/base/core/proto/android/os/bundle.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto index 1e6ee3f1a3a8..6749719be124 100644 --- a/core/proto/android/server/notificationhistory.proto +++ b/core/proto/android/server/notificationhistory.proto @@ -17,8 +17,6 @@ syntax = "proto2"; package com.android.server.notification; -import "frameworks/base/core/proto/android/server/enums.proto"; - option java_multiple_files = true; // On disk data store for historical notifications diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto new file mode 100644 index 000000000000..8598f86a7f28 --- /dev/null +++ b/core/proto/android/service/sensor_service.proto @@ -0,0 +1,246 @@ +/* + * 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.service; + +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; + +/* + * Notes: + * 1. When using ProtoOutputStream to write this proto message, must call + * token = ProtoOutputStream#start(fieldId) before and ProtoOutputStream#end(token) after + * writing a nested message. + * 2. Never reuse a proto field number. When removing a field, mark it as reserved. + */ + +// Proto dump of android::SensorService. dumpsys sensorservice --proto +message SensorServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + enum OperatingModeEnum { + OP_MODE_UNKNOWN = 0; + OP_MODE_NORMAL = 1; + OP_MODE_RESTRICTED = 2; + OP_MODE_DATA_INJECTION = 3; + } + + optional int64 current_time_ms = 1; + optional SensorDeviceProto sensor_device = 2; + optional SensorListProto sensors = 3; + optional SensorFusionProto fusion_state = 4; + optional SensorEventsProto sensor_events = 5; + repeated ActiveSensorProto active_sensors = 6; + optional int32 socket_buffer_size = 7; + optional int32 socket_buffer_size_in_events = 8; + optional bool wake_lock_acquired = 9; + optional OperatingModeEnum operating_mode = 10; + // Non-empty only if operating_mode is RESTRICTED or DATA_INJECTION. + optional string whitelisted_package = 11; + optional bool sensor_privacy = 12; + repeated SensorEventConnectionProto active_connections = 13; + repeated SensorDirectConnectionProto direct_connections = 14; + repeated SensorRegistrationInfoProto previous_registrations = 15; +} + +// Proto dump of android::SensorDevice +message SensorDeviceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool initialized = 1; + optional int32 total_sensors = 2; + optional int32 active_sensors = 3; + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 handle = 1; + optional int32 active_count = 2; + repeated float sampling_period_ms = 3; + optional float sampling_period_selected = 4; + repeated float batching_period_ms = 5; + optional float batching_period_selected = 6; + } + repeated SensorProto sensors = 4; +} + +// Proto dump of android::SensorServiceUtil::SensorList +message SensorListProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + enum ReportingModeEnum { + RM_UNKNOWN = 0; + RM_CONTINUOUS = 1; + RM_ON_CHANGE = 2; + RM_ONE_SHOT = 3; + RM_SPECIAL_TRIGGER = 4; + } + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 handle = 1; + optional string name = 2; + optional string vendor = 3; + optional int32 version = 4; + optional string string_type = 5; + optional int32 type = 6; + optional string required_permission = 7; + optional int32 flags = 8; + optional ReportingModeEnum reporting_mode = 9; + optional int32 max_delay_us = 10; + optional int32 min_delay_us = 11; + optional int32 fifo_max_event_count = 12; + optional int32 fifo_reserved_event_count = 13; + optional bool is_wakeup = 14; + optional bool data_injection_supported = 15; + optional bool is_dynamic = 16; + optional bool has_additional_info = 17; + optional int32 highest_rate_level = 18; + optional bool ashmem = 19; + optional bool gralloc = 20; + optional float min_value = 21; + optional float max_value = 22; + optional float resolution = 23; + optional float power_usage = 24; + } + repeated SensorProto sensors = 1; +} + + +// Proto dump of android::SensorFusion +message SensorFusionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message FusionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool enabled = 1; + optional int32 num_clients = 2; + optional float estimated_gyro_rate = 3; + optional float attitude_x = 4; + optional float attitude_y = 5; + optional float attitude_z = 6; + optional float attitude_w = 7; + optional float attitude_length = 8; + optional float bias_x = 9; + optional float bias_y = 10; + optional float bias_z = 11; + } + optional FusionProto fusion_9axis = 1; + optional FusionProto fusion_nomag = 2; + optional FusionProto fusion_nogyro = 3; +} + +// Proto dump of android::SensorServiceUtil::RecentEventLogger +message SensorEventsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message Event { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional float timestamp_sec = 1; + optional int64 wall_timestamp_ms = 2; + optional bool masked = 3; + optional int64 int64_data = 4; + repeated float float_array = 5; + } + + message RecentEventsLog { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + optional int32 recent_events_count = 2; + repeated Event events = 3; + } + repeated RecentEventsLog recent_events_logs = 1; +} + +message ActiveSensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + optional int32 handle = 2; + optional int32 num_connections = 3; +} + +// Proto dump of android::SensorService::SensorDirectConnection +message SensorDirectConnectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 sensor = 1; + optional int32 rate = 2; + } + + optional string package_name = 1; + optional int32 hal_channel_handle = 2; + optional int32 num_sensor_activated = 3; + repeated SensorProto sensors = 4; +} + +// Proto dump of android::SensorService::SensorEventConnection +message SensorEventConnectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message FlushInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string sensor_name = 1; + optional int32 sensor_handle = 2; + optional bool first_flush_pending = 3; + optional int32 pending_flush_events_to_send = 4; + } + + enum OperatingModeEnum { + OP_MODE_UNKNOWN = 0; + OP_MODE_NORMAL = 1; + OP_MODE_RESTRICTED = 2; + OP_MODE_DATA_INJECTION = 3; + } + + optional OperatingModeEnum operating_mode = 1; + optional string package_name = 2; + optional int32 wake_lock_ref_count = 3; + optional int32 uid = 4; + optional int32 cache_size = 5; + optional int32 max_cache_size = 6; + repeated FlushInfoProto flush_infos = 7; + optional int32 events_received = 8; + optional int32 events_sent = 9; + optional int32 events_cache = 10; + optional int32 events_dropped = 11; + optional int32 total_acks_needed = 12; + optional int32 total_acks_received = 13; +} + +// Proto dump of android::SensorService::SensorRegistrationInfo +message SensorRegistrationInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 timestamp_sec = 1; + optional int32 sensor_handle = 2; + optional string package_name = 3; + optional int32 pid = 4; + optional int32 uid = 5; + optional int64 sampling_rate_us = 6; + optional int64 max_report_latency_us = 7; + optional bool activated = 8; +} diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 0f03e69e6c93..d1392a5e0f31 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -157,4 +157,6 @@ enum EventId { SET_FACTORY_RESET_PROTECTION = 130; SET_COMMON_CRITERIA_MODE = 131; ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132; + SET_TIME = 133; + SET_TIME_ZONE = 134; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 753d64a3359a..de0d172ecf04 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2603,9 +2603,9 @@ <!-- Allows telephony to suggest the time / time zone. <p>Not for use by third-party applications. - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide + @hide --> - <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE" + <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE" android:protectionLevel="signature|telephony" /> <!-- Allows applications like settings to suggest the user's manually chosen time / time zone. @@ -3448,6 +3448,14 @@ <permission android:name="android.permission.TUNER_RESOURCE_ACCESS" android:protectionLevel="signature|privileged" /> + <!-- This permission is required by Media Resource Manager Service when + accessing its overridePid Api. + <p>Protection level: signature + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @hide --> @@ -4057,6 +4065,11 @@ <permission android:name="android.permission.STATSCOMPANION" android:protectionLevel="signature" /> + <!--@SystemApi @hide Allows an application to register stats pull atom callbacks. + <p>Not for use by third-party applications.--> + <permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to control the backup and restore process. <p>Not for use by third-party applications. @hide pending API council --> @@ -4865,6 +4878,19 @@ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" android:protectionLevel="signature|installer" /> + <!-- Allows an app to log compat change usage. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.LOG_COMPAT_CHANGE" + android:protectionLevel="signature|privileged" /> + <!-- Allows an app to read compat change config. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" + android:protectionLevel="signature|privileged" /> + <!-- Allows an app to override compat change config. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" + android:protectionLevel="signature|privileged" /> + <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature" /> @@ -5075,6 +5101,12 @@ android:process=":ui"> </activity> + <activity android:name="com.android.internal.app.BlockedAppActivity" + android:theme="@style/Theme.Dialog.Confirmation" + android:excludeFromRecents="true" + android:process=":ui"> + </activity> + <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity" android:theme="@style/Theme.Dialog.Confirmation" android:excludeFromRecents="true"> @@ -5296,6 +5328,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.blob.BlobStoreIdleJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml index d19e313055ae..d6fd7aa30e6e 100644 --- a/core/res/res/layout/accessibility_button_chooser_item.xml +++ b/core/res/res/layout/accessibility_button_chooser_item.xml @@ -20,6 +20,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:clickable="true" android:gravity="center" android:paddingStart="16dp" android:paddingEnd="16dp" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3d7b1e18be7b..31e68e88c0a8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3560,25 +3560,16 @@ --> <string name="config_defaultAutofillService" translatable="false"></string> - <!-- The package name for the default system textclassifier service. + <!-- The package name for the OEM custom system textclassifier service. This service must be trusted, as it can be activated without explicit consent of the user. Example: "com.android.textclassifier" - If no textclassifier service with the specified name exists on the device (or if this is - set to empty string), a default textclassifier will be loaded in the calling app's process. + If this is empty, the default textclassifier service (i.e. config_servicesExtensionPackage) + will be used. + See android.view.textclassifier.TextClassificationManager. --> - <!-- TODO(b/144896755) remove the config --> <string name="config_defaultTextClassifierPackage" translatable="false"></string> - <!-- A list of supported system textClassifier service package names. Only one of the packages - will be activated. The first package in the list is the default system textClassifier - service. OS only tries to bind and grant permissions to the first trusted service and the - others can be selected via device config. These services must be trusted, as they can be - activated without explicit consent of the user. Example: "com.android.textclassifier" - --> - <string-array name="config_defaultTextClassifierPackages" translatable="false"> - <item>android.ext.services</item> - </string-array> <!-- The package name for the system companion device manager service. This service must be trusted, as it can be activated without explicit consent of the user. @@ -4352,4 +4343,7 @@ Determines whether the specified key groups can be used to wake up the device. --> <bool name="config_wakeOnDpadKeyPress">true</bool> <bool name="config_wakeOnAssistKeyPress">true</bool> + + <!-- Whether to default to an expanded list of users on the lock screen user switcher. --> + <bool name="config_expandLockScreenUserSwitcher">false</bool> </resources> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 51ed08b88111..bddda1bf6f6f 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -226,4 +226,7 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PRESS_AND_HOLD}. --> <item type="id" name="accessibilityActionPressAndHold" /> + + <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER}. --> + <item type="id" name="accessibilityActionImeEnter" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4f61730ad2e2..4172044dfa13 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3066,6 +3066,7 @@ <public name="accessibilitySystemActionToggleSplitScreen" /> <public name="accessibilitySystemActionLockScreen" /> <public name="accessibilitySystemActionTakeScreenshot" /> + <public name="accessibilityActionImeEnter" /> </public-group> <public-group type="array" first-id="0x01070006"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 11cc36544632..a54566cfed17 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4936,6 +4936,13 @@ <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] --> <string name="work_mode_turn_on">Turn on</string> + <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] --> + <string name="app_blocked_title">App is not available</string> + <!-- Default message shown in the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=NONE] --> + <string name="app_blocked_message"> + <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now. + </string> + <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] --> <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string> <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 36296a850dcb..2453bb18577f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3048,6 +3048,9 @@ <java-symbol type="string" name="app_suspended_unsuspend_message" /> <java-symbol type="string" name="app_suspended_default_message" /> + <java-symbol type="string" name="app_blocked_title" /> + <java-symbol type="string" name="app_blocked_message" /> + <!-- Used internally for assistant to launch activity transitions --> <java-symbol type="id" name="cross_task_transition" /> @@ -3390,7 +3393,6 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultTextClassifierPackage" /> - <java-symbol type="array" name="config_defaultTextClassifierPackages" /> <java-symbol type="string" name="config_defaultWellbeingPackage" /> <java-symbol type="string" name="config_telephonyPackages" /> <java-symbol type="string" name="config_defaultContentCaptureService" /> @@ -3861,4 +3863,7 @@ <!-- 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" /> + + <!-- Whether to expand the lock screen user switcher by default --> + <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" /> </resources> diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml index 6cf6a8273f5f..9110661536e2 100644 --- a/core/res/res/xml/default_zen_mode_config.xml +++ b/core/res/res/xml/default_zen_mode_config.xml @@ -18,9 +18,10 @@ --> <!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. --> -<zen version="8"> +<zen version="9"> <allow alarms="true" media="true" system="false" calls="true" callsFrom="2" messages="false" - reminders="false" events="false" repeatCallers="true" /> + reminders="false" events="false" repeatCallers="true" conversations="true" + conversationsFrom="2"/> <automatic ruleId="EVENTS_DEFAULT_RULE" enabled="false" snoozing="false" name="Event" zen="1" component="android/com.android.server.notification.EventConditionProvider" conditionId="condition://android/event?userId=-10000&calendar=&reply=1"/> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 2d1c61cc22fc..70917e76f8b4 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,11 +34,14 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="3214|1017" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> + <!-- Argentia: 5 digits, known short codes listed --> + <shortcode country="ar" pattern="\\d{5}" free="11711|28291" /> + <!-- Armenia: 3-4 digits, emergency numbers 10[123] --> <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" /> @@ -80,7 +83,7 @@ <shortcode country="cn" premium="1066.*" free="1065.*" /> <!-- Colombia: 1-6 digits (not confirmed) --> - <shortcode country="co" pattern="\\d{1,6}" free="890350|908160" /> + <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960" /> <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" /> @@ -181,7 +184,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963|76551" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" /> @@ -196,7 +199,7 @@ <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" /> <!-- New Zealand: 3-4 digits, known premium codes listed --> - <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> + <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> <shortcode country="pe" pattern="\\d{4,5}" free="9963" /> @@ -264,4 +267,7 @@ <!-- Mayotte (French Territory): 1-5 digits (not confirmed) --> <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" /> + <!-- South Africa --> + <shortcode country="za" pattern="\d{1,5}" free="44136" /> + </shortcodes> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 38454ecb6111..9f15fafe2bf1 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -38,6 +38,7 @@ android_test { "mockito-target-minus-junit4", "ub-uiautomator", "platform-test-annotations", + "platform-compat-test-rules", "truth-prebuilt", "print-test-util-lib", "testng", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index ee93b397bedf..59335a595334 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -111,6 +111,10 @@ <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> + <!-- gating and logging permissions --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <!-- os storage test permissions --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.ASEC_ACCESS" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index 6c5d5485664e..02be557c01bd 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -24,6 +24,9 @@ import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.content.pm.ConfigurationInfo; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; import android.test.AndroidTestCase; import androidx.test.filters.SmallTest; @@ -137,21 +140,7 @@ public class ActivityManagerTest extends AndroidTestCase { // Must overwrite all the fields td2.copyFrom(td1); - assertEquals(td1.getLabel(), td2.getLabel()); - assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td1.getIconFilename(), td2.getIconFilename()); - assertEquals(td1.getIconResource(), td2.getIconResource()); - assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td1, td2, true, true); } @SmallTest @@ -191,44 +180,101 @@ public class ActivityManagerTest extends AndroidTestCase { // Must overwrite all public and hidden fields, since other has all fields set. td2.copyFromPreserveHiddenFields(td1); - assertEquals(td1.getLabel(), td2.getLabel()); - assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td1.getIconFilename(), td2.getIconFilename()); - assertEquals(td1.getIconResource(), td2.getIconResource()); - assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td1, td2, true, true); TaskDescription td3 = new TaskDescription(); // Must overwrite only public fields, and preserve hidden fields. td2.copyFromPreserveHiddenFields(td3); - // Overwritten fields - assertEquals(td3.getLabel(), td2.getLabel()); - assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td3.getIconFilename(), td2.getIconFilename()); - assertEquals(td3.getIconResource(), td2.getIconResource()); - assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - - // Preserved fields - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td3, td2, true, false); + assertTaskDescriptionEqual(td1, td2, false, true); + } + + @SmallTest + public void testTaskDescriptionParceling() throws Exception { + TaskDescription tdBitmapNull = new TaskDescription( + "test label", // label + null, // bitmap + 21, // iconRes + "dummy file", // iconFilename + 0x111111, // colorPrimary + 0x222222, // colorBackground + 0x333333, // statusBarColor + 0x444444, // navigationBarColor + false, // ensureStatusBarContrastWhenTransparent + false, // ensureNavigationBarContrastWhenTransparent + RESIZE_MODE_UNRESIZEABLE, // resizeMode + 10, // minWidth + 20 // minHeight + ); + + // Normal parceling should keep everything the same. + TaskDescription tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapNull)); + assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true); + + Bitmap recycledBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); + recycledBitmap.recycle(); + assertTrue(recycledBitmap.isRecycled()); + TaskDescription tdBitmapRecycled = new TaskDescription( + "test label", // label + recycledBitmap, // bitmap + 21, // iconRes + "dummy file", // iconFilename + 0x111111, // colorPrimary + 0x222222, // colorBackground + 0x333333, // statusBarColor + 0x444444, // navigationBarColor + false, // ensureStatusBarContrastWhenTransparent + false, // ensureNavigationBarContrastWhenTransparent + RESIZE_MODE_UNRESIZEABLE, // resizeMode + 10, // minWidth + 20 // minHeight + ); + // Recycled bitmap will be ignored while parceling. + tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled)); + assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true); + + } + + private void assertTaskDescriptionEqual(TaskDescription td1, TaskDescription td2, + boolean checkOverwrittenFields, boolean checkPreservedFields) { + if (checkOverwrittenFields) { + assertEquals(td1.getLabel(), td2.getLabel()); + assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); + assertEquals(td1.getIconFilename(), td2.getIconFilename()); + assertEquals(td1.getIconResource(), td2.getIconResource()); + assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); + assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), + td2.getEnsureStatusBarContrastWhenTransparent()); + assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), + td2.getEnsureNavigationBarContrastWhenTransparent()); + } + if (checkPreservedFields) { + assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); + assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); + assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); + assertEquals(td1.getResizeMode(), td2.getResizeMode()); + assertEquals(td1.getMinWidth(), td2.getMinWidth()); + assertEquals(td1.getMinHeight(), td2.getMinHeight()); + } + } + + private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception { + final Parcel p = Parcel.obtain(); + in.writeToParcel(p, /* flags */ 0); + p.setDataPosition(0); + final byte[] marshalledData = p.marshall(); + p.recycle(); + + final Parcel q = Parcel.obtain(); + q.unmarshall(marshalledData, 0, marshalledData.length); + q.setDataPosition(0); + + final Parcelable.Creator<T> creator = (Parcelable.Creator<T>) + in.getClass().getField("CREATOR").get(null); // static object, so null receiver + final T unmarshalled = (T) creator.createFromParcel(q); + q.recycle(); + return unmarshalled; } // If any entries in appear in the list, sanity check them against all running applications diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index c986db8b2a83..c328d720426d 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -504,15 +504,17 @@ public class ActivityThreadTest { } @Override - public void onPictureInPictureRequested() { + public boolean onPictureInPictureRequested() { mPipRequested = true; if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) { enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); mPipEntered = true; + return true; } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) { mPipEnterSkipped = true; + return false; } - super.onPictureInPictureRequested(); + return super.onPictureInPictureRequested(); } boolean pipRequested() { diff --git a/core/tests/coretests/src/android/app/compat/CompatChangesTest.java b/core/tests/coretests/src/android/app/compat/CompatChangesTest.java new file mode 100644 index 000000000000..fbd02eddbcf8 --- /dev/null +++ b/core/tests/coretests/src/android/app/compat/CompatChangesTest.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.app.compat; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Instrumentation; +import android.compat.testing.PlatformCompatChangeRule; +import android.os.Process; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * {@link CompatChanges} tests. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class CompatChangesTest { + static final long CHANGE_ID = 1L; + + private Instrumentation mInstrumentation; + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Before + public void setup() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + + private String getPackageName() { + return mInstrumentation.getTargetContext().getPackageName(); + } + + @Test + @EnableCompatChanges(CHANGE_ID) + public void testEnabledChange() { + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isTrue(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isTrue(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(), + UserHandle.of(UserHandle.myUserId()))).isTrue(); + } + + @Test + @DisableCompatChanges(CHANGE_ID) + public void testDisabledChange() { + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isFalse(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isFalse(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(), + UserHandle.of(UserHandle.myUserId()))).isFalse(); + } +} diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java index d17b63597a21..4b64dfc84fb7 100644 --- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java @@ -26,44 +26,45 @@ import android.os.TimestampedValue; import org.junit.Test; -public class PhoneTimeSuggestionTest { +public class TelephonyTimeSuggestionTest { private static final int SLOT_INDEX = 99999; @Test public void testEquals() { - PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder1 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); { - PhoneTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion one = builder1.build(); assertEquals(one, one); } - PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder2 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); assertEquals(two, one); } builder1.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion one = builder1.build(); assertEquals(one, one); } builder2.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); assertEquals(two, one); } - PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(SLOT_INDEX + 1); + TelephonyTimeSuggestion.Builder builder3 = + new TelephonyTimeSuggestion.Builder(SLOT_INDEX + 1); builder3.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion three = builder3.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion three = builder3.build(); assertNotEquals(one, three); assertNotEquals(three, one); } @@ -72,15 +73,15 @@ public class PhoneTimeSuggestionTest { builder1.addDebugInfo("Debug info 1"); builder2.addDebugInfo("Debug info 2"); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); } } @Test public void testParcelable() { - PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); assertRoundTripParcelable(builder.build()); builder.setUtcTime(new TimestampedValue<>(1111L, 2222L)); @@ -88,9 +89,9 @@ public class PhoneTimeSuggestionTest { // DebugInfo should also be stored (but is not checked by equals() { - PhoneTimeSuggestion suggestion1 = builder.build(); + TelephonyTimeSuggestion suggestion1 = builder.build(); builder.addDebugInfo("This is debug info"); - PhoneTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1); + TelephonyTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1); assertEquals(suggestion1.getDebugInfo(), rtSuggestion1.getDebugInfo()); } } diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java deleted file mode 100644 index 384dbf9fab07..000000000000 --- a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 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.app.timezonedetector; - -import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; -import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class PhoneTimeZoneSuggestionTest { - private static final int SLOT_INDEX = 99999; - - @Test - public void testEquals() { - PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - { - PhoneTimeZoneSuggestion one = builder1.build(); - assertEquals(one, one); - } - - PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - assertEquals(two, one); - } - - PhoneTimeZoneSuggestion.Builder builder3 = - new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX + 1); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion three = builder3.build(); - assertNotEquals(one, three); - assertNotEquals(three, one); - } - - builder1.setZoneId("Europe/London"); - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder2.setZoneId("Europe/Paris"); - builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setZoneId("Europe/Paris"); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); - builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - // DebugInfo must not be considered in equals(). - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - one.addDebugInfo("Debug info 1"); - two.addDebugInfo("Debug info 2"); - assertEquals(one, two); - } - } - - @Test(expected = RuntimeException.class) - public void testBuilderValidates_emptyZone_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - // No zone ID, so match type should be left unset. - builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET); - builder.build(); - } - - @Test(expected = RuntimeException.class) - public void testBuilderValidates_zoneSet_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - builder.setZoneId("Europe/London"); - builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - builder.build(); - } - - @Test - public void testParcelable() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - assertRoundTripParcelable(builder.build()); - - builder.setZoneId("Europe/London"); - builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); - builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - PhoneTimeZoneSuggestion suggestion1 = builder.build(); - assertRoundTripParcelable(suggestion1); - - // DebugInfo should also be stored (but is not checked by equals() - String debugString = "This is debug info"; - suggestion1.addDebugInfo(debugString); - PhoneTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1); - assertEquals(suggestion1, suggestion1_2); - assertTrue(suggestion1_2.getDebugInfo().contains(debugString)); - } -} diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java new file mode 100644 index 000000000000..59d55b79157c --- /dev/null +++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java @@ -0,0 +1,162 @@ +/* + * Copyright 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.app.timezonedetector; + +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; +import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class TelephonyTimeZoneSuggestionTest { + private static final int SLOT_INDEX = 99999; + + @Test + public void testEquals() { + TelephonyTimeZoneSuggestion.Builder builder1 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + assertEquals(one, one); + } + + TelephonyTimeZoneSuggestion.Builder builder2 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + assertEquals(two, one); + } + + TelephonyTimeZoneSuggestion.Builder builder3 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX + 1); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion three = builder3.build(); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + + builder1.setZoneId("Europe/London"); + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder2.setZoneId("Europe/Paris"); + builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + builder2.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setZoneId("Europe/Paris"); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); + builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + builder2.setQuality( + TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setQuality( + TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + // DebugInfo must not be considered in equals(). + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + one.addDebugInfo("Debug info 1"); + two.addDebugInfo("Debug info 2"); + assertEquals(one, two); + } + } + + @Test(expected = RuntimeException.class) + public void testBuilderValidates_emptyZone_badMatchType() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + // No zone ID, so match type should be left unset. + builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET); + builder.build(); + } + + @Test(expected = RuntimeException.class) + public void testBuilderValidates_zoneSet_badMatchType() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + builder.setZoneId("Europe/London"); + builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + builder.build(); + } + + @Test + public void testParcelable() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + assertRoundTripParcelable(builder.build()); + + builder.setZoneId("Europe/London"); + builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); + builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + TelephonyTimeZoneSuggestion suggestion1 = builder.build(); + assertRoundTripParcelable(suggestion1); + + // DebugInfo should also be stored (but is not checked by equals() + String debugString = "This is debug info"; + suggestion1.addDebugInfo(debugString); + TelephonyTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1); + assertEquals(suggestion1, suggestion1_2); + assertTrue(suggestion1_2.getDebugInfo().contains(debugString)); + } +} diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java index bf782034c8f1..3273e5dd32ba 100644 --- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java @@ -90,18 +90,18 @@ public class AtomicFormulaTest { } @Test - public void testValidAtomicFormula_stringValue_appCertificateAutoHashed() { + public void testValidAtomicFormula_stringValue_appCertificateIsNotAutoHashed() { String appCert = "cert"; StringAtomicFormula stringAtomicFormula = new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCert); assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCert); + assertThat(stringAtomicFormula.getValue()).matches(appCert); assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test - public void testValidAtomicFormula_stringValue_installerCertificateAutoHashed() { + public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() { String installerCert = "cert"; StringAtomicFormula stringAtomicFormula = new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, @@ -109,7 +109,7 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo( AtomicFormula.INSTALLER_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCert); + assertThat(stringAtomicFormula.getValue()).matches(installerCert); assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index c1806028f75b..75ef1f22b819 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -40,7 +40,7 @@ public class IntegrityFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.PACKAGE_NAME); assertThat(stringAtomicFormula.getValue()).isEqualTo(packageName); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test @@ -53,8 +53,8 @@ public class IntegrityFormulaTest { (AtomicFormula.StringAtomicFormula) formula; assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCertificate); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true); + assertThat(stringAtomicFormula.getValue()).matches(appCertificate); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test @@ -68,7 +68,7 @@ public class IntegrityFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_NAME); assertThat(stringAtomicFormula.getValue()).isEqualTo(installerName); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test @@ -81,8 +81,8 @@ public class IntegrityFormulaTest { (AtomicFormula.StringAtomicFormula) formula; assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCertificate); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true); + assertThat(stringAtomicFormula.getValue()).matches(installerCertificate); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java index decc76869a53..2295eb989108 100644 --- a/core/tests/coretests/src/android/os/BuildTest.java +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -60,7 +60,7 @@ public class BuildTest extends TestCase { assertNotEmpty("BRAND", Build.BRAND); assertNotEmpty("MODEL", Build.MODEL); assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL); - assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE); + assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE_OR_CODENAME); assertNotEmpty("TYPE", Build.TYPE); Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty. assertNotEmpty("FINGERPRINT", Build.FINGERPRINT); diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 84c42dbc14e7..d649b945492b 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -376,6 +376,24 @@ public class DeviceConfigTest { } @Test + public void resetToDefault_makeDefault() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, true); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + } + + @Test + public void resetToDefault_doNotMakeDefault() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isNull(); + } + + @Test public void getProperties_fullNamespace() { Properties properties = DeviceConfig.getProperties(NAMESPACE); assertThat(properties.getKeyset()).isEmpty(); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 8b8e9ea4c6ee..b71c5800161e 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -19,7 +19,6 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; @@ -157,9 +156,5 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon return -1; } - public Bitmap takeScreenshot(int displayId) { - return null; - } - - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} + public void takeScreenshot(int displayId, RemoteCallback callback) {} } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java index 8faf790549d5..1ca46491b363 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.Intent; import android.os.LocaleList; -import android.service.textclassifier.TextClassifierService; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -64,9 +63,7 @@ public class TextClassificationManagerTest { @Test public void testGetSystemTextClassifier() { - assertTrue( - TextClassifierService.getServiceComponentName(mContext) == null - || mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier); + assertTrue(mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier); } @Test diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 2304ba6f6da4..372a4787cf1a 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.LocaleList; +import android.service.textclassifier.TextClassifierService; import android.text.Spannable; import android.text.SpannableString; @@ -58,11 +59,12 @@ import java.util.List; public class TextClassifierTest { private static final String LOCAL = "local"; private static final String SESSION = "session"; + private static final String DEFAULT = "default"; // TODO: Add SYSTEM, which tests TextClassifier.SYSTEM. @Parameterized.Parameters(name = "{0}") public static Iterable<Object> textClassifierTypes() { - return Arrays.asList(LOCAL, SESSION); + return Arrays.asList(LOCAL, SESSION, DEFAULT); } @Parameterized.Parameter @@ -84,13 +86,15 @@ public class TextClassifierTest { if (mTextClassifierType.equals(LOCAL)) { mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL); - } else { + } else if (mTextClassifierType.equals(SESSION)) { mClassifier = mTcm.createTextClassificationSession( new TextClassificationContext.Builder( "android", TextClassifier.WIDGET_TYPE_NOTIFICATION) .build(), mTcm.getTextClassifier(TextClassifier.LOCAL)); + } else { + mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(mContext); } } @@ -369,16 +373,14 @@ public class TextClassifierTest { mClassifier.generateLinks(request).apply(url, 0, null)); } - - @Test(expected = IllegalArgumentException.class) + @Test public void testGenerateLinks_tooLong() { - if (isTextClassifierDisabled()) { - throw new IllegalArgumentException("pass if disabled"); - } + if (isTextClassifierDisabled()) return; char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1]; Arrays.fill(manySpaces, ' '); TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build(); - mClassifier.generateLinks(request); + TextLinks links = mClassifier.generateLinks(request); + assertTrue(links.getLinks().isEmpty()); } @Test diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index df6b9066ea5c..ce71bebfc455 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -38,6 +38,7 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -355,6 +356,7 @@ public class ChooserActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -1209,17 +1211,24 @@ public class ChooserActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; int personalProfileTargets = 3; + int otherProfileTargets = 1; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(personalProfileTargets); + createResolvedComponentsForTestWithOtherProfile( + personalProfileTargets + otherProfileTargets, /* userID */ 10); int workProfileTargets = 4; List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( workProfileTargets); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); markWorkProfileUserAvailable(); @@ -1229,8 +1238,6 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - // The work list adapter must only be filled when we open the work tab - assertThat(activity.getWorkListAdapter().getCount(), is(0)); onView(withText(R.string.resolver_work_tab)).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); @@ -1243,11 +1250,22 @@ public class ChooserActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); @@ -1357,6 +1375,20 @@ public class ChooserActivityTest { return infoList; } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( + int numberOfResults, int userId) { + List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + if (i == 0) { + infoList.add( + ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId)); + } else { + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); + } + } + return infoList; + } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId( int numberOfResults, int userId) { List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index eee62bb791bf..a68b59086d42 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -16,10 +16,15 @@ package com.android.internal.app; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; import android.app.usage.UsageStatsManager; import android.content.ContentResolver; import android.content.Context; @@ -40,6 +45,8 @@ import com.android.internal.app.chooser.TargetInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import org.mockito.Mockito; + import java.util.function.Function; public class ChooserWrapperActivity extends ChooserActivity { @@ -173,6 +180,12 @@ public class ChooserWrapperActivity extends ChooserActivity { return mMultiProfilePagerAdapter.getCurrentUserHandle(); } + @Override + public Context createContextAsUser(UserHandle user, int flags) { + // return the current context as a work profile doesn't really exist in these tests + return getApplicationContext(); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 911490f30799..5f4194aa51e3 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -225,6 +225,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -246,7 +247,6 @@ public class ResolverActivityTest { chosen[0] = targetInfo.getResolveInfo(); return true; }; - // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); @@ -443,7 +443,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), @@ -451,6 +451,11 @@ public class ResolverActivityTest { when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -478,17 +483,20 @@ public class ResolverActivityTest { Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), @@ -502,7 +510,7 @@ public class ResolverActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(3)); + assertThat(activity.getPersonalListAdapter().getCount(), is(2)); } @Test @@ -511,14 +519,20 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); @@ -536,14 +550,20 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { @@ -587,17 +607,20 @@ public class ResolverActivityTest { Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), @@ -678,6 +701,20 @@ public class ResolverActivityTest { return infoList; } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( + int numberOfResults, int userId) { + List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + if (i == 0) { + infoList.add( + ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId)); + } else { + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); + } + } + return infoList; + } + private void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index a200a510cf12..fe1182ecad4f 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -26,6 +26,7 @@ <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> + <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.MANAGE_DEBUGGING"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> @@ -37,8 +38,10 @@ <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> + <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.READ_SEARCH_INDEXABLES"/> <permission name="android.permission.REBOOT"/> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index da505505628e..6929d0d1879d 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -72,6 +72,11 @@ <group gid="net_admin" /> </permission> + <permission name="android.permission.MAINLINE_NETWORK_STACK" > + <group gid="net_admin" /> + <group gid="net_raw" /> + </permission> + <!-- The group that /cache belongs to, linked to the permission set on the applications that can access /cache --> <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" > diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index b5eba090691f..f83fb3f312e9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -169,7 +169,7 @@ applications that come with the platform <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.STOP_APP_SWITCHES"/> - <permission name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"/> + <permission name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.UPDATE_LOCK"/> @@ -366,6 +366,10 @@ applications that come with the platform <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> <!-- Permission required for Tethering CTS tests. --> <permission name="android.permission.TETHER_PRIVILEGED"/> + <!-- Permissions required for ganting and logging --> + <permission name="android.permission.LOG_COMPAT_CHANGE" /> + <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" /> <!-- Permissions required to test ambient display. --> <permission name="android.permission.READ_DREAM_STATE" /> <permission name="android.permission.WRITE_DREAM_STATE" /> diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 3b864139cf71..d08bfcf45a5c 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -157,7 +157,7 @@ public class HardwareRenderer { public HardwareRenderer() { mRootNode = RenderNode.adopt(nCreateRootRenderNode()); mRootNode.setClipToBounds(false); - mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode); + mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode); if (mNativeProxy == 0) { throw new OutOfMemoryError("Unable to create hardware renderer"); } @@ -1085,7 +1085,8 @@ public class HardwareRenderer { private static native long nCreateRootRenderNode(); - private static native long nCreateProxy(boolean translucent, long rootRenderNode); + private static native long nCreateProxy(boolean translucent, boolean isWideGamut, + long rootRenderNode); private static native void nDeleteProxy(long nativeProxy); diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index dcc6b95aec02..129063361b35 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -38,6 +38,10 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { ICredentialStoreFactory storeFactory = ICredentialStoreFactory.Stub.asInterface( ServiceManager.getService("android.security.identity")); + if (storeFactory == null) { + // This can happen if credstore is not running or not installed. + return null; + } ICredentialStore credStore = null; try { diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 3681c69e912b..a3d552faeb0a 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -187,7 +187,9 @@ private: EGLSyncKHR fence = mUploadThread->queue().runSync([&]() -> EGLSyncKHR { AutoSkiaGlTexture glTexture; glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); - GL_CHECKPOINT(MODERATE); + if (GLUtils::dumpGLErrors()) { + return EGL_NO_SYNC_KHR; + } // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we // provide. @@ -195,19 +197,26 @@ private: // when we first use it in drawing glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format.format, format.type, bitmap.getPixels()); - GL_CHECKPOINT(MODERATE); + if (GLUtils::dumpGLErrors()) { + return EGL_NO_SYNC_KHR; + } EGLSyncKHR uploadFence = eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); - LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR, - "Could not create sync fence %#x", eglGetError()); + if (uploadFence == EGL_NO_SYNC_KHR) { + ALOGW("Could not create sync fence %#x", eglGetError()); + }; glFlush(); + GLUtils::dumpGLErrors(); return uploadFence; }); + if (fence == EGL_NO_SYNC_KHR) { + return false; + } EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT); - LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, - "Failed to wait for the fence %#x", eglGetError()); + ALOGE_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, + "Failed to wait for the fence %#x", eglGetError()); eglDestroySyncKHR(display, fence); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index e7efe2ff798b..8d5acc631274 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -171,17 +171,15 @@ static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) { } bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { + uint32_t extraBuffers) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } - setSurfaceColorProperties(colorMode); - if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); + auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace); if (!newSurface) { return false; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 3fe0f92b1924..e482cad6c953 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -44,7 +44,7 @@ public: FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 6f4af3d26b3a..29b4dd7f32e7 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -44,6 +44,7 @@ namespace uirenderer { namespace skiapipeline { SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { + setSurfaceColorProperties(mColorMode); } SkiaPipeline::~SkiaPipeline() { @@ -584,6 +585,7 @@ void SkiaPipeline::dumpResourceCacheUsage() const { } void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { + mColorMode = colorMode; if (colorMode == ColorMode::SRGB) { mSurfaceColorType = SkColorType::kN32_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index af8414de4bc1..8341164edc19 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -50,6 +50,7 @@ public: bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, ErrorHandler* errorHandler) override; + void setSurfaceColorProperties(renderthread::ColorMode colorMode) override; SkColorType getSurfaceColorType() const override { return mSurfaceColorType; } sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } @@ -72,9 +73,10 @@ public: protected: void dumpResourceCacheUsage() const; - void setSurfaceColorProperties(renderthread::ColorMode colorMode); renderthread::RenderThread& mRenderThread; + + renderthread::ColorMode mColorMode = renderthread::ColorMode::SRGB; SkColorType mSurfaceColorType; sk_sp<SkColorSpace> mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index ad7c70614239..535a19956e03 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -117,17 +117,16 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { + uint32_t extraBuffers) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); mVkSurface = nullptr; } - setSurfaceColorProperties(colorMode); if (surface) { mRenderThread.requireVkContext(); mVkSurface = - mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, mSurfaceColorType, + mVkManager.createSurface(surface, mColorMode, mSurfaceColorSpace, mSurfaceColorType, mRenderThread.getGrContext(), extraBuffers); } diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 31734783de7f..c8bf233d8e1c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -43,7 +43,7 @@ public: FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c1435d1ea2d5..91f9447a3d59 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -161,9 +161,8 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { mRenderAheadCapacity = mRenderAheadDepth; } - ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface( - mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, colorMode, + mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, mRenderAheadCapacity); mFrameNumber = -1; @@ -174,7 +173,7 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { // Enable frame stats after the surface has been bound to the appropriate graphics API. // Order is important when new and old surfaces are the same, because old surface has // its frame stats disabled automatically. - mNativeSurface->enableFrameTimestamps(true); + native_window_enable_frame_timestamps(mNativeSurface->getNativeWindow(), true); } else { mRenderThread.removeFrameCallback(this); mGenerationID++; @@ -225,7 +224,8 @@ void CanvasContext::setOpaque(bool opaque) { } void CanvasContext::setWideGamut(bool wideGamut) { - mWideColorGamut = wideGamut; + ColorMode colorMode = wideGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; + mRenderPipeline->setSurfaceColorProperties(colorMode); } bool CanvasContext::makeCurrent() { @@ -429,7 +429,8 @@ void CanvasContext::setPresentTime() { if (renderAhead) { presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) + - (frameIntervalNanos * (renderAhead + 1)); + (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() + + (frameIntervalNanos / 2); } native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime); } @@ -555,8 +556,9 @@ void CanvasContext::draw() { FrameInfo* forthBehind = mLast4FrameInfos.front().first; int64_t composedFrameId = mLast4FrameInfos.front().second; nsecs_t acquireTime = -1; - mNativeSurface->getFrameTimestamps(composedFrameId, nullptr, &acquireTime, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr); + native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId, + nullptr, &acquireTime, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr); // Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1; mJankTracker.finishGpuDraw(*forthBehind); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 0967b20e44ee..629c741e8757 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -251,7 +251,6 @@ private: nsecs_t mLastDropVsync = 0; bool mOpaque; - bool mWideColorGamut = false; bool mUseForceDark = false; LightInfo mLightInfo; LightGeometry mLightGeometry = {{0, 0, 0}, 0}; diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index ef0aa98d4220..ba0d64c5492d 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -66,7 +66,7 @@ public: virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode, + virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, uint32_t extraBuffers) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; @@ -80,6 +80,8 @@ public: virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0; virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0; virtual void unpinImages() = 0; + + virtual void setSurfaceColorProperties(ColorMode colorMode) = 0; virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; virtual GrSurfaceOrigin getSurfaceOrigin() = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h index da5097ce33f0..e3cd8c019a23 100644 --- a/libs/hwui/renderthread/ReliableSurface.h +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -49,21 +49,6 @@ public: return ret; } - status_t getFrameTimestamps(uint64_t frameNumber, - nsecs_t* outRequestedPresentTime, nsecs_t* outAcquireTime, - nsecs_t* outLatchTime, nsecs_t* outFirstRefreshStartTime, - nsecs_t* outLastRefreshStartTime, nsecs_t* outGlCompositionDoneTime, - nsecs_t* outDisplayPresentTime, nsecs_t* outDequeueReadyTime, - nsecs_t* outReleaseTime) { - return mSurface->getFrameTimestamps(frameNumber, outRequestedPresentTime, outAcquireTime, - outLatchTime, outFirstRefreshStartTime, outLastRefreshStartTime, - outGlCompositionDoneTime, outDisplayPresentTime, outDequeueReadyTime, outReleaseTime); - } - - void enableFrameTimestamps(bool enable) { - return mSurface->enableFrameTimestamps(enable); - } - private: sp<Surface> mSurface; diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 307d13606cb8..90bcd1c0e370 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -398,7 +398,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { auto surface = context.surface(); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); EXPECT_FALSE(pipeline->isSurfaceReady()); - EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, ColorMode::SRGB, 0)); + EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, 0)); EXPECT_TRUE(pipeline->isSurfaceReady()); renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp index 150f6dcde5d0..512b8c439dcf 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -cc_library_shared { - name: "libincident", + +cc_defaults { + name: "libincidentpriv_defaults", cflags: [ "-Wall", @@ -50,6 +51,70 @@ cc_library_shared { ":libincident_aidl", "src/IncidentReportArgs.cpp", ], +} + +cc_library_shared { + name: "libincidentpriv", + defaults: ["libincidentpriv_defaults"], + export_include_dirs: ["include_priv"], +} + +cc_library_shared { + name: "libincident", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wunused-parameter", + ], + + shared_libs: [ + "libbinder", + "liblog", + "libutils", + "libincidentpriv", + ], + + srcs: [ + "src/incident_report.cpp", + ], export_include_dirs: ["include"], + + stubs: { + symbol_file: "libincident.map.txt", + versions: [ + "30", + ], + }, } + +cc_test { + name: "libincident_test", + defaults: ["libincidentpriv_defaults"], + test_suites: ["device-tests"], + + include_dirs: [ + "frameworks/base/libs/incident/include", + "frameworks/base/libs/incident/include_priv", + ], + + srcs: [ + "tests/IncidentReportArgs_test.cpp", + "tests/IncidentReportRequest_test.cpp", + "tests/c_api_compile_test.c", + ], + + shared_libs: [ + "libincident", + ], + + static_libs: [ + "libgmock", + ], +} + + + diff --git a/libs/incident/include/incident/incident_report.h b/libs/incident/include/incident/incident_report.h new file mode 100644 index 000000000000..49fe5b9b73b4 --- /dev/null +++ b/libs/incident/include/incident/incident_report.h @@ -0,0 +1,192 @@ +/** + * 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. + */ + +/** + * @file incident_report.h + */ + +#ifndef ANDROID_INCIDENT_INCIDENT_REPORT_H +#define ANDROID_INCIDENT_INCIDENT_REPORT_H + +#include <stdbool.h> + +#if __cplusplus +#include <set> +#include <string> +#include <vector> + +extern "C" { +#endif // __cplusplus + +struct AIncidentReportArgs; +/** + * Opaque class to represent the arguments to an incident report request. + * Incident reports contain debugging data about the device at runtime. + * For more information see the android.os.IncidentManager java class. + */ +typedef struct AIncidentReportArgs AIncidentReportArgs; + +// Privacy policy enum value, sync with frameworks/base/core/proto/android/privacy.proto, +// IncidentReportArgs.h and IncidentReportArgs.java. +enum { + /** + * Flag marking fields and incident reports than can be taken + * off the device only via adb. + */ + INCIDENT_REPORT_PRIVACY_POLICY_LOCAL = 0, + + /** + * Flag marking fields and incident reports than can be taken + * off the device with contemporary consent. + */ + INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT = 100, + + /** + * Flag marking fields and incident reports than can be taken + * off the device with prior consent. + */ + INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC = 200, + + /** + * Flag to indicate that a given field has not been marked + * with a privacy policy. + */ + INCIDENT_REPORT_PRIVACY_POLICY_UNSET = 255 +}; + +/** + * Allocate and initialize an AIncidentReportArgs object. + */ +AIncidentReportArgs* AIncidentReportArgs_init(); + +/** + * Duplicate an existing AIncidentReportArgs object. + */ +AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that); + +/** + * Clean up and delete an AIncidentReportArgs object. + */ +void AIncidentReportArgs_delete(AIncidentReportArgs* args); + +/** + * Set this incident report to include all sections. + */ +void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all); + +/** + * Set this incident report privacy policy spec. + */ +void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy); + +/** + * Add this section to the incident report. The section IDs are the field numbers + * from the android.os.IncidentProto protobuf message. + */ +void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section); + +/** + * Set the apk package name that will be sent a broadcast when the incident + * report completes. Must be called in conjunction with AIncidentReportArgs_setReceiverClass. + */ +void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg); + +/** + * Set the fully qualified class name of the java BroadcastReceiver class that will be + * sent a broadcast when the report completes. Must be called in conjunction with + * AIncidentReportArgs_setReceiverPackage. + */ +void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls); + +/** + * Add protobuf data as a header to the incident report. The buffer should be a serialized + * android.os.IncidentHeaderProto object. + */ +void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size); + +/** + * Initiate taking the report described in the args object. Returns 0 on success, + * and non-zero otherwise. + */ +int AIncidentReportArgs_takeReport(AIncidentReportArgs* args); + +#if __cplusplus +} // extern "C" + +namespace android { +namespace os { + +class IncidentReportRequest { +public: + inline IncidentReportRequest() { + mImpl = AIncidentReportArgs_init(); + } + + inline IncidentReportRequest(const IncidentReportRequest& that) { + mImpl = AIncidentReportArgs_clone(that.mImpl); + } + + inline ~IncidentReportRequest() { + AIncidentReportArgs_delete(mImpl); + } + + inline AIncidentReportArgs* getImpl() { + return mImpl; + } + + inline void setAll(bool all) { + AIncidentReportArgs_setAll(mImpl, all); + } + + inline void setPrivacyPolicy(int privacyPolicy) { + AIncidentReportArgs_setPrivacyPolicy(mImpl, privacyPolicy); + } + + inline void addSection(int section) { + AIncidentReportArgs_addSection(mImpl, section); + } + + inline void setReceiverPackage(const std::string& pkg) { + AIncidentReportArgs_setReceiverPackage(mImpl, pkg.c_str()); + }; + + inline void setReceiverClass(const std::string& cls) { + AIncidentReportArgs_setReceiverClass(mImpl, cls.c_str()); + }; + + inline void addHeader(const std::vector<uint8_t>& headerProto) { + AIncidentReportArgs_addHeader(mImpl, headerProto.data(), headerProto.size()); + }; + + inline void addHeader(const uint8_t* buf, size_t size) { + AIncidentReportArgs_addHeader(mImpl, buf, size); + }; + + // returns a status_t + inline int takeReport() { + return AIncidentReportArgs_takeReport(mImpl); + } + +private: + AIncidentReportArgs* mImpl; +}; + +} // namespace os +} // namespace android + +#endif // __cplusplus + +#endif // ANDROID_INCIDENT_INCIDENT_REPORT_H diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include_priv/android/os/IncidentReportArgs.h index 94b4ad6eae31..0e6159032e45 100644 --- a/libs/incident/include/android/os/IncidentReportArgs.h +++ b/libs/incident/include_priv/android/os/IncidentReportArgs.h @@ -14,9 +14,10 @@ * limitations under the License. */ -#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_ -#define ANDROID_OS_DUMPSTATE_ARGS_H_ +#ifndef ANDROID_OS_INCIDENT_REPORT_ARGS_H +#define ANDROID_OS_INCIDENT_REPORT_ARGS_H +#include <binder/IServiceManager.h> #include <binder/Parcel.h> #include <binder/Parcelable.h> #include <utils/String16.h> @@ -29,7 +30,8 @@ namespace os { using namespace std; -// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto +// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto, +// incident/incident_report.h and IncidentReportArgs.java const uint8_t PRIVACY_POLICY_LOCAL = 0; const uint8_t PRIVACY_POLICY_EXPLICIT = 100; const uint8_t PRIVACY_POLICY_AUTOMATIC = 200; @@ -74,4 +76,4 @@ private: } } -#endif // ANDROID_OS_DUMPSTATE_ARGS_H_ +#endif // ANDROID_OS_INCIDENT_REPORT_ARGS_H diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt new file mode 100644 index 000000000000..f157763f1a03 --- /dev/null +++ b/libs/incident/libincident.map.txt @@ -0,0 +1,15 @@ +LIBINCIDENT { + global: + AIncidentReportArgs_init; # apex # introduced=30 + AIncidentReportArgs_clone; # apex # introduced=30 + AIncidentReportArgs_delete; # apex # introduced=30 + AIncidentReportArgs_setAll; # apex # introduced=30 + AIncidentReportArgs_setPrivacyPolicy; # apex # introduced=30 + AIncidentReportArgs_addSection; # apex # introduced=30 + AIncidentReportArgs_setReceiverPackage; # apex # introduced=30 + AIncidentReportArgs_setReceiverClass; # apex # introduced=30 + AIncidentReportArgs_addHeader; # apex # introduced=30 + AIncidentReportArgs_takeReport; # apex # introduced=30 + local: + *; +}; diff --git a/libs/incident/src/incident_report.cpp b/libs/incident/src/incident_report.cpp new file mode 100644 index 000000000000..7897ddf6d251 --- /dev/null +++ b/libs/incident/src/incident_report.cpp @@ -0,0 +1,83 @@ +/** + * 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. + */ + +#define LOG_TAG "libincident" + +#include <incident/incident_report.h> + +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IServiceManager.h> +#include <binder/Status.h> +#include <log/log.h> + +using android::sp; +using android::binder::Status; +using android::os::IncidentReportArgs; +using android::os::IIncidentManager; +using std::string; +using std::vector; + +AIncidentReportArgs* AIncidentReportArgs_init() { + return reinterpret_cast<AIncidentReportArgs*>(new IncidentReportArgs()); +} + +AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that) { + return reinterpret_cast<AIncidentReportArgs*>( + new IncidentReportArgs(*reinterpret_cast<IncidentReportArgs*>(that))); +} + +void AIncidentReportArgs_delete(AIncidentReportArgs* args) { + delete reinterpret_cast<IncidentReportArgs*>(args); +} + +void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all) { + reinterpret_cast<IncidentReportArgs*>(args)->setAll(all); +} + +void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy) { + reinterpret_cast<IncidentReportArgs*>(args)->setPrivacyPolicy(privacyPolicy); +} + +void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section) { + reinterpret_cast<IncidentReportArgs*>(args)->addSection(section); +} + +void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg) { + reinterpret_cast<IncidentReportArgs*>(args)->setReceiverPkg(string(pkg)); +} + +void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls) { + reinterpret_cast<IncidentReportArgs*>(args)->setReceiverCls(string(cls)); +} + +void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size) { + vector<uint8_t> vec(buf, buf+size); + reinterpret_cast<IncidentReportArgs*>(args)->addHeader(vec); +} + +int AIncidentReportArgs_takeReport(AIncidentReportArgs* argp) { + IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(argp); + + sp<IIncidentManager> service = android::interface_cast<IIncidentManager>( + android::defaultServiceManager()->getService(android::String16("incident"))); + if (service == nullptr) { + ALOGW("Failed to fetch incident service."); + return false; + } + Status s = service->reportIncident(*args); + return s.transactionError(); +} diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/libs/incident/tests/IncidentReportArgs_test.cpp index 38bc19452afa..224b343c554a 100644 --- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp +++ b/libs/incident/tests/IncidentReportArgs_test.cpp @@ -20,6 +20,8 @@ namespace android { namespace os { namespace statsd { +// Checks that all of the inline methods on IncidentReportRequest and the real C functions +// result in a working IncidentReportArgs. TEST(IncidentReportArgsTest, testSerialization) { IncidentReportArgs args; args.setAll(0); diff --git a/libs/incident/tests/IncidentReportRequest_test.cpp b/libs/incident/tests/IncidentReportRequest_test.cpp new file mode 100644 index 000000000000..6d218b6682a3 --- /dev/null +++ b/libs/incident/tests/IncidentReportRequest_test.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2018 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. + +#include <android/os/IncidentReportArgs.h> +#include <incident/incident_report.h> + +#include <gtest/gtest.h> + +namespace android { +namespace os { +namespace statsd { + +TEST(IncidentReportRequestTest, testWrite) { + IncidentReportRequest request; + request.setAll(0); + request.addSection(1000); + request.addSection(1001); + + vector<uint8_t> header1; + header1.push_back(0x1); + header1.push_back(0x2); + vector<uint8_t> header2; + header1.push_back(0x22); + header1.push_back(0x33); + + request.addHeader(header1); + request.addHeader(header2); + + request.setPrivacyPolicy(1); + + request.setReceiverPackage("com.android.os"); + request.setReceiverClass("com.android.os.Receiver"); + + IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(request.getImpl()); + + EXPECT_EQ(0, args->all()); + set<int> sections; + sections.insert(1000); + sections.insert(1001); + EXPECT_EQ(sections, args->sections()); + EXPECT_EQ(1, args->getPrivacyPolicy()); + + EXPECT_EQ(string("com.android.os"), args->receiverPkg()); + EXPECT_EQ(string("com.android.os.Receiver"), args->receiverCls()); + + vector<vector<uint8_t>> headers; + headers.push_back(header1); + headers.push_back(header2); + EXPECT_EQ(headers, args->headers()); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/libs/incident/tests/c_api_compile_test.c b/libs/incident/tests/c_api_compile_test.c new file mode 100644 index 000000000000..e1620dfe3280 --- /dev/null +++ b/libs/incident/tests/c_api_compile_test.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <incident/incident_report.h> + +/* + * This file ensures that incident/incident_report.h actually compiles with C, + * since there is no other place in the tree that actually uses it from C. + */ +int not_called() { + return 0; +} + diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 197787e5b6e6..1c10edb11293 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -33,6 +33,9 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.app.AlarmManager; import android.app.PendingIntent; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -50,7 +53,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ProviderProperties; @@ -82,6 +84,36 @@ public class LocationManager { private static final String TAG = "LocationManager"; /** + * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a + * specific package. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN) + public static final long TARGETED_PENDING_INTENT = 148963590L; + + /** + * For apps targeting Android K and above, incomplete locations may not be passed to + * {@link #setTestProviderLocation}. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN) + private static final long INCOMPLETE_LOCATION = 148964793L; + + /** + * For apps targeting Android S and above, all {@link GpsStatus} API usage must be replaced with + * {@link GnssStatus} APIs. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GPS_STATUS_USAGE = 144027538L; + + /** * Name of the network location provider. * * <p>This provider determines location based on nearby of cell tower and WiFi access points. @@ -771,7 +803,6 @@ public class LocationManager { public void requestSingleUpdate(@NonNull String provider, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(provider != null, "invalid null provider"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, 0, 0, true); @@ -800,7 +831,6 @@ public class LocationManager { public void requestSingleUpdate(@NonNull Criteria criteria, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(criteria != null, "invalid null criteria"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, 0, 0, true); @@ -1021,7 +1051,6 @@ public class LocationManager { public void requestLocationUpdates(@NonNull String provider, long minTimeMs, float minDistanceM, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(provider != null, "invalid null provider"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, minTimeMs, minDistanceM, false); @@ -1048,7 +1077,6 @@ public class LocationManager { public void requestLocationUpdates(long minTimeMs, float minDistanceM, @NonNull Criteria criteria, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(criteria != null, "invalid null criteria"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, minTimeMs, minDistanceM, false); @@ -1164,9 +1192,9 @@ public class LocationManager { @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(locationRequest != null, "invalid null location request"); Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { Preconditions.checkArgument(pendingIntent.isTargetedToPackage(), - "pending intent must be targeted to package"); + "pending intent must be targeted to a package"); } try { @@ -1198,15 +1226,9 @@ public class LocationManager { */ @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION}) public boolean injectLocation(@NonNull Location location) { - if (location == null) { - IllegalArgumentException e = new IllegalArgumentException("invalid null location"); - if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) { - throw e; - } else { - Log.w(TAG, e); - return false; - } - } + Preconditions.checkArgument(location != null, "invalid null location"); + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); try { return mService.injectLocation(location); @@ -1487,15 +1509,11 @@ public class LocationManager { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(location != null, "invalid null location"); - if (!location.isComplete()) { - IllegalArgumentException e = new IllegalArgumentException( - "Incomplete location object, missing timestamp or accuracy? " + location); - if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) { - Log.w(TAG, e); - location.makeComplete(); - } else { - throw e; - } + if (Compatibility.isChangeEnabled(INCOMPLETE_LOCATION)) { + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); + } else { + location.makeComplete(); } try { @@ -1629,7 +1647,11 @@ public class LocationManager { @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) public void addProximityAlert(double latitude, double longitude, float radius, long expiration, @NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } if (expiration < 0) expiration = Long.MAX_VALUE; Geofence fence = Geofence.createCircle(latitude, longitude, radius); @@ -1659,7 +1681,11 @@ public class LocationManager { * permission is not present */ public void removeProximityAlert(@NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(null, intent, mContext.getPackageName()); @@ -1709,8 +1735,13 @@ public class LocationManager { @NonNull LocationRequest request, @NonNull Geofence fence, @NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(request != null, "invalid null location request"); Preconditions.checkArgument(fence != null, "invalid null geofence"); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.requestGeofence(request, fence, intent, mContext.getPackageName(), @@ -1737,8 +1768,12 @@ public class LocationManager { * @hide */ public void removeGeofence(@NonNull Geofence fence, @NonNull PendingIntent intent) { - checkPendingIntent(intent); Preconditions.checkArgument(fence != null, "invalid null geofence"); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(fence, intent, mContext.getPackageName()); @@ -1759,7 +1794,11 @@ public class LocationManager { * @hide */ public void removeAllGeofences(@NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(null, intent, mContext.getPackageName()); @@ -1833,14 +1872,15 @@ public class LocationManager { * @param status object containing GPS status details, or null. * @return status object containing updated GPS status. * - * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead. + * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead. No longer + * supported in apps targeting S and above. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) { - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { throw new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus(); @@ -1863,17 +1903,14 @@ public class LocationManager { * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present * * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer - * supported in apps targeting R and above. + * supported in apps targeting S and above. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) public boolean addGpsStatusListener(GpsStatus.Listener listener) { - UnsupportedOperationException ex = new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { - throw ex; - } else { - Log.w(TAG, ex); + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { + throw new UnsupportedOperationException( + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } try { @@ -1889,16 +1926,13 @@ public class LocationManager { * @param listener GPS status listener object to remove * * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. No longer - * supported in apps targeting R and above. + * supported in apps targeting S and above. */ @Deprecated public void removeGpsStatusListener(GpsStatus.Listener listener) { - UnsupportedOperationException ex = new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { - throw ex; - } else { - Log.w(TAG, ex); + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { + throw new UnsupportedOperationException( + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } try { @@ -2397,19 +2431,6 @@ public class LocationManager { } } - private void checkPendingIntent(PendingIntent pendingIntent) { - Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent"); - if (!pendingIntent.isTargetedToPackage()) { - IllegalArgumentException e = new IllegalArgumentException( - "invalid pending intent - must be targeted to package"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { - throw e; - } else { - Log.w(TAG, e); - } - } - } - private static class GetCurrentLocationTransport extends ILocationListener.Stub implements AlarmManager.OnAlarmListener { diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index f3613d3665ca..42841d178401 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -229,13 +230,19 @@ public final class AudioRecordingConfiguration implements Parcelable { * <p>This information is only available if the caller has the * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} * permission. - * <br>The result is -1 without the permission. * @return the user id + * @throws SecurityException Thrown if the caller is missing the MODIFY_AUDIO_ROUTING permission * * @hide */ @SystemApi - public int getClientUid() { return mClientUid; } + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getClientUid() { + if (mClientUid == -1) { + throw new SecurityException("MODIFY_AUDIO_ROUTING permission is missing"); + } + return mClientUid; + } /** * Returns information about the audio input device used for this recording. diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index a25aff6611ca..9131f3bc960d 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -18,6 +18,7 @@ package android.media; import android.content.Intent; import android.media.IMediaRoute2ProviderClient; +import android.media.RouteDiscoveryPreference; import android.os.Bundle; /** @@ -28,6 +29,7 @@ oneway interface IMediaRoute2Provider { void requestCreateSession(String packageName, String routeId, long requestId, in @nullable Bundle sessionHints); void releaseSession(String sessionId); + void updateDiscoveryPreference(in RouteDiscoveryPreference discoveryPreference); void selectRoute(String sessionId, String routeId); void deselectRoute(String sessionId, String routeId); @@ -35,5 +37,4 @@ oneway interface IMediaRoute2Provider { void notifyControlRequestSent(String id, in Intent request); void requestSetVolume(String id, int volume); - void requestUpdateVolume(String id, int delta); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index dac0fba876be..6fef46889742 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -52,7 +52,6 @@ interface IMediaRouterService { void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request); void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume); - void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId, in @nullable Bundle sessionHints); @@ -70,8 +69,6 @@ interface IMediaRouterService { void requestSetVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int volume); - void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - in MediaRoute2Info route, int direction); List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager); void selectClientRoute(IMediaRouter2Manager manager, diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index e39b7bc59148..20a59bba54c6 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -46,13 +46,20 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Base class for media route provider services. * <p> + * Media route provider services are used to publish {@link MediaRoute2Info media routes} such as + * speakers, TVs, etc. The routes are published by calling {@link #notifyRoutes(Collection)}. + * Media apps which use {@link MediaRouter2} can request to play their media on the routes. + * </p><p> + * When {@link MediaRouter2 media router} wants to play media on a route, + * {@link #onCreateSession(String, String, long, Bundle)} will be called to handle the request. + * A session can be considered as a group of currently selected routes for each connection. + * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo + * session infos} when there are any changes. + * </p><p> * The system media router service will bind to media route provider services when a * {@link RouteDiscoveryPreference discovery preference} is registered via - * a {@link MediaRouter2 media router} by an application. - * </p><p> - * To implement your own media route provider service, extend this class and - * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish - * {@link MediaRoute2Info routes}. + * a {@link MediaRouter2 media router} by an application. See + * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details. * </p> */ public abstract class MediaRoute2ProviderService extends Service { @@ -118,22 +125,15 @@ public abstract class MediaRoute2ProviderService extends Service { public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); /** - * Called when requestSetVolume is called on a route of the provider + * Called when requestSetVolume is called on a route of the provider. * * @param routeId the id of the route * @param volume the target volume + * @see MediaRoute2Info#getVolumeMax() */ public abstract void onSetVolume(@NonNull String routeId, int volume); /** - * Called when requestUpdateVolume is called on a route of the provider - * - * @param routeId id of the route - * @param delta the delta to add to the current volume - */ - public abstract void onUpdateVolume(@NonNull String routeId, int delta); - - /** * Gets information of the session with the given id. * * @param sessionId id of the session @@ -370,7 +370,6 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param preference the new discovery preference */ - // TODO: This method needs tests. public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {} /** @@ -456,6 +455,16 @@ public abstract class MediaRoute2ProviderService extends Service { } @Override + public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + if (!checkCallerisSystem()) { + return; + } + mHandler.sendMessage(obtainMessage( + MediaRoute2ProviderService::onDiscoveryPreferenceChanged, + MediaRoute2ProviderService.this, discoveryPreference)); + } + + @Override public void selectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; @@ -511,14 +520,5 @@ public abstract class MediaRoute2ProviderService extends Service { mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume, MediaRoute2ProviderService.this, routeId, volume)); } - - @Override - public void requestUpdateVolume(String routeId, int delta) { - if (!checkCallerisSystem()) { - return; - } - mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume, - MediaRoute2ProviderService.this, routeId, delta)); - } } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index f751a22c4900..d7b74df0a5e6 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -182,11 +182,16 @@ public class MediaRouter2 { Client2 client = new Client2(); try { mMediaRouterService.registerClient2(client, mPackageName); - updateDiscoveryRequestLocked(); - mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference); mClient = client; } catch (RemoteException ex) { - Log.e(TAG, "Unable to register media router.", ex); + Log.e(TAG, "registerRouteCallback: Unable to register client.", ex); + } + } + if (mClient != null && updateDiscoveryPreferenceIfNeededLocked()) { + try { + mMediaRouterService.setDiscoveryRequest2(mClient, mDiscoveryPreference); + } catch (RemoteException ex) { + Log.e(TAG, "registerRouteCallback: Unable to set discovery request."); } } } @@ -209,22 +214,37 @@ public class MediaRouter2 { } synchronized (sRouterLock) { - if (mRouteCallbackRecords.size() == 0 && mClient != null) { - try { - mMediaRouterService.unregisterClient2(mClient); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to unregister media router.", ex); + if (mClient != null) { + if (updateDiscoveryPreferenceIfNeededLocked()) { + try { + mMediaRouterService.setDiscoveryRequest2(mClient, mDiscoveryPreference); + } catch (RemoteException ex) { + Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request."); + } + } + if (mRouteCallbackRecords.size() == 0) { + try { + mMediaRouterService.unregisterClient2(mClient); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to unregister media router.", ex); + } } - //TODO: Clean up mRoutes. (onHandler?) + mShouldUpdateRoutes = true; mClient = null; } } } - private void updateDiscoveryRequestLocked() { - mDiscoveryPreference = new RouteDiscoveryPreference.Builder( + private boolean updateDiscoveryPreferenceIfNeededLocked() { + RouteDiscoveryPreference newDiscoveryPreference = new RouteDiscoveryPreference.Builder( mRouteCallbackRecords.stream().map(record -> record.mPreference).collect( Collectors.toList())).build(); + if (Objects.equals(mDiscoveryPreference, newDiscoveryPreference)) { + return false; + } + mDiscoveryPreference = newDiscoveryPreference; + mShouldUpdateRoutes = true; + return true; } /** @@ -442,31 +462,6 @@ public class MediaRouter2 { } } - /** - * Requests an incremental volume update for the route asynchronously. - * <p> - * It may have no effect if the route is currently not selected. - * </p> - * - * @param delta The delta to add to the current volume. - * @hide - */ - public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { - Objects.requireNonNull(route, "route must not be null"); - - Client2 client; - synchronized (sRouterLock) { - client = mClient; - } - if (client != null) { - try { - mMediaRouterService.requestUpdateVolume2(client, route, delta); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); - } - } - } - void addRoutesOnHandler(List<MediaRoute2Info> routes) { // TODO: When onRoutesAdded is first called, // 1) clear mRoutes before adding the routes diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 662eeb1418f1..b1f930d0f2cc 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -294,30 +294,6 @@ public class MediaRouter2Manager { } } - /** - * Requests an incremental volume update for the route asynchronously. - * <p> - * It may have no effect if the route is currently not selected. - * </p> - * - * @param delta The delta to add to the current volume. - */ - public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { - Objects.requireNonNull(route, "route must not be null"); - - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - mMediaRouterService.requestUpdateVolume2Manager(client, route, delta); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); - } - } - } - void addRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : routes) { diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index 7ec1123ee19c..ebcb9ed342e5 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -31,7 +31,7 @@ import java.util.Objects; import java.util.Set; /** - * A media route discovery preference describing the kinds of routes that media router + * A media route discovery preference describing the kinds of routes that media router * would like to discover and whether to perform active scanning. * * @see MediaRouter2#registerRouteCallback @@ -58,6 +58,7 @@ public final class RouteDiscoveryPreference implements Parcelable { private final Bundle mExtras; /** + * An empty discovery preference. * @hide */ public static final RouteDiscoveryPreference EMPTY = diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 118f65c214e1..08953392ca18 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -113,7 +113,7 @@ public final class SoundTriggerDetector { * This capability may or may not be supported by the system, and support can be queried * by calling {@link SoundTriggerManager#getModuleProperties()} and checking * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for - * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}. + * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_ECHO_CANCELLATION}. * If this flag is passed without the audio capability supported, there will be no audio effect * applied. */ @@ -125,8 +125,9 @@ public final class SoundTriggerDetector { * This capability may or may not be supported by the system, and support can be queried * by calling {@link SoundTriggerManager#getModuleProperties()} and checking * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for - * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag - * is passed without the audio capability supported, there will be no audio effect applied. + * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_NOISE_SUPPRESSION}. + * If this flag is passed without the audio capability supported, there will be no audio effect + * applied. */ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; @@ -296,10 +297,10 @@ public final class SoundTriggerDetector { int audioCapabilities = 0; if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { - audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; } if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { - audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; } int status; diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index dd4dac223a86..6a8483c58276 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -173,8 +173,13 @@ public final class SoundTriggerManager { } /** - * Factory constructor to create a SoundModel instance for use with methods in this - * class. + * Factory constructor to a voice model to be used with {@link SoundTriggerManager} + * + * @param modelUuid Unique identifier associated with the model. + * @param vendorUuid Unique identifier associated the calling vendor. + * @param data Model's data. + * @param version Version identifier for the model. + * @return Voice model */ @NonNull public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, @@ -186,8 +191,12 @@ public final class SoundTriggerManager { } /** - * Factory constructor to create a SoundModel instance for use with methods in this - * class. + * Factory constructor to a voice model to be used with {@link SoundTriggerManager} + * + * @param modelUuid Unique identifier associated with the model. + * @param vendorUuid Unique identifier associated the calling vendor. + * @param data Model's data. + * @return Voice model */ @NonNull public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, @@ -195,20 +204,40 @@ public final class SoundTriggerManager { return create(modelUuid, vendorUuid, data, -1); } + /** + * Get the model's unique identifier + * + * @return UUID associated with the model + */ @NonNull public UUID getModelUuid() { return mGenericSoundModel.uuid; } + /** + * Get the model's vendor identifier + * + * @return UUID associated with the vendor of the model + */ @NonNull public UUID getVendorUuid() { return mGenericSoundModel.vendorUuid; } + /** + * Get the model's version + * + * @return Version associated with the model + */ public int getVersion() { return mGenericSoundModel.version; } + /** + * Get the underlying model data + * + * @return Backing data of the model + */ @Nullable public byte[] getModelData() { return mGenericSoundModel.data; diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 09b755912c43..433c6227cd5f 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -1109,6 +1109,24 @@ public final class TvContract { * <p>Type: TEXT */ String COLUMN_SERIES_ID = "series_id"; + + /** + * The split ID of this TV program for multi-part content, as a URI. + * + * <p>A content may consist of multiple programs within the same channel or over several + * channels. For example, a film might be divided into two parts interrupted by a news in + * the middle or a longer sport event might be split into several parts over several + * channels. The split ID is used to identify all the programs in the same multi-part + * content. Suitable URIs include + * <ul> + * <li>{@code crid://<CRIDauthority>/<data>#<IMI>} from ETSI TS 102 323 + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + String COLUMN_SPLIT_ID = "split_id"; } /** @@ -1677,6 +1695,7 @@ public final class TvContract { TYPE_ATSC_T, TYPE_ATSC_C, TYPE_ATSC_M_H, + TYPE_ATSC3_T, TYPE_ISDB_T, TYPE_ISDB_TB, TYPE_ISDB_S, @@ -1801,6 +1820,13 @@ public final class TvContract { public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H"; /** + * The channel type for ATSC3.0 (terrestrial). + * + * @see #COLUMN_TYPE + */ + public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T"; + + /** * The channel type for ISDB-T (terrestrial). * * @see #COLUMN_TYPE @@ -2022,6 +2048,7 @@ public final class TvContract { * {@link #TYPE_ATSC_C}, * {@link #TYPE_ATSC_M_H}, * {@link #TYPE_ATSC_T}, + * {@link #TYPE_ATSC3_T}, * {@link #TYPE_CMMB}, * {@link #TYPE_DTMB}, * {@link #TYPE_DVB_C}, @@ -2407,6 +2434,22 @@ public final class TvContract { */ public static final String COLUMN_TRANSIENT = "transient"; + /** + * The global content ID of this TV channel, as a URI. + * + * <p>A globally unique URI that identifies this TV channel, if applicable. Suitable URIs + * include + * <ul> + * <li>{@code globalServiceId} from ATSC A/331. ex {@code https://doi.org/10.5239/7E4E-B472} + * <li>Other broadcast ID provider. ex {@code http://example.com/tv_channel/1234} + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + private Channels() {} /** @@ -2562,6 +2605,37 @@ public final class TvContract { */ public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; + /** + * The event ID of this TV program. + * + * <p>It is used to identify the current TV program in the same channel, if applicable. + * Use the same coding for {@code event_id} in the underlying broadcast standard if it + * is defined there (e.g. ATSC A/65, ETSI EN 300 468 and ARIB STD-B10). + * + * <p>This is a required field only if the underlying broadcast standard defines the same + * name field. Otherwise, leave empty. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_EVENT_ID = "event_id"; + + /** + * The global content ID of this TV program, as a URI. + * + * <p>A globally unique ID that identifies this TV program, if applicable. Suitable URIs + * include + * <ul> + * <li>{@code crid://<CRIDauthority>/<data>} from ETSI TS 102 323 + * <li>{@code globalContentId} from ATSC A/332 + * <li>Other broadcast ID provider. ex {@code http://example.com/tv_program/1234} + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + private Programs() {} /** Canonical genres for TV programs. */ diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 5e012447e9dd..3561f8393eea 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -74,6 +74,8 @@ public class Tuner implements AutoCloseable { private List<Integer> mFrontendIds; private Frontend mFrontend; private EventHandler mHandler; + @Nullable + private FrontendInfo mFrontendInfo; private List<Integer> mLnbIds; private Lnb mLnb; @@ -97,6 +99,7 @@ public class Tuner implements AutoCloseable { public Tuner(@NonNull Context context, @NonNull String tvInputSessionId, @TvInputService.PriorityHintUseCaseType int useCase, @Nullable OnResourceLostListener listener) { + nativeSetup(); mContext = context; } @@ -185,7 +188,7 @@ public class Tuner implements AutoCloseable { /** * Listener for resource lost. * - * <p>Resource is reclaimed and tuner instance is forced to close. + * <p>Insufficient resources are reclaimed by higher priority clients. */ public interface OnResourceLostListener { /** @@ -292,6 +295,7 @@ public class Tuner implements AutoCloseable { @Result public int tune(@NonNull FrontendSettings settings) { TunerUtils.checkTunerPermission(mContext); + mFrontendInfo = null; return nativeTune(settings.getType(), settings); } @@ -333,6 +337,7 @@ public class Tuner implements AutoCloseable { } mScanCallback = scanCallback; mScanCallbackExecutor = executor; + mFrontendInfo = null; return nativeScan(settings.getType(), settings, scanType); } @@ -468,7 +473,10 @@ public class Tuner implements AutoCloseable { if (mFrontend == null) { throw new IllegalStateException("frontend is not initialized"); } - return nativeGetFrontendInfo(mFrontend.mId); + if (mFrontendInfo == null) { + mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId); + } + return mFrontendInfo; } /** diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 9b37f955fa17..71ba59c8a707 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -724,18 +724,21 @@ status_t JMediaCodec::getOutputFrame( } if (buffer->size() > 0) { - // asC2Buffer clears internal reference, so set the reference again. std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); - buffer->copy(c2Buffer); if (c2Buffer) { + // asC2Buffer clears internal reference, so set the reference again. + buffer->copy(c2Buffer); switch (c2Buffer->data().type()) { case C2BufferData::LINEAR: { std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; context->mBuffer = c2Buffer; ScopedLocalRef<jobject> linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; - env->SetLongField( - linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + linearBlock.get(), + gLinearBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); break; } @@ -744,8 +747,11 @@ status_t JMediaCodec::getOutputFrame( context->mBuffer = c2Buffer; ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; - env->SetLongField( - graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + graphicBlock.get(), + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); break; } @@ -761,16 +767,22 @@ status_t JMediaCodec::getOutputFrame( context->mLegacyBuffer = buffer; ScopedLocalRef<jobject> linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; - env->SetLongField( - linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + linearBlock.get(), + gLinearBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); } else { std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; context->mLegacyBuffer = buffer; ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; - env->SetLongField( - graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + graphicBlock.get(), + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 1fbe7f42fff3..08c3f988e85e 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -322,6 +322,179 @@ jobject JTuner::openFrontendById(int id) { (jint) jId); } +jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint typeCap = caps.analogCaps().typeCap; + jint sifStandardCap = caps.analogCaps().sifStandardCap; + return env->NewObject(clazz, capsInit, typeCap, sifStandardCap); +} + +jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3FrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIII)V"); + + jint bandwidthCap = caps.atsc3Caps().bandwidthCap; + jint modulationCap = caps.atsc3Caps().modulationCap; + jint timeInterleaveModeCap = caps.atsc3Caps().timeInterleaveModeCap; + jint codeRateCap = caps.atsc3Caps().codeRateCap; + jint fecCap = caps.atsc3Caps().fecCap; + jint demodOutputFormatCap = caps.atsc3Caps().demodOutputFormatCap; + + return env->NewObject(clazz, capsInit, bandwidthCap, modulationCap, timeInterleaveModeCap, + codeRateCap, fecCap, demodOutputFormatCap); +} + +jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AtscFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V"); + + jint modulationCap = caps.atscCaps().modulationCap; + + return env->NewObject(clazz, capsInit, modulationCap); +} + +jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbcFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(III)V"); + + jint modulationCap = caps.dvbcCaps().modulationCap; + jint fecCap = caps.dvbcCaps().fecCap; + jint annexCap = caps.dvbcCaps().annexCap; + + return env->NewObject(clazz, capsInit, modulationCap, fecCap, annexCap); +} + +jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbsFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IJI)V"); + + jint modulationCap = caps.dvbsCaps().modulationCap; + jlong innerfecCap = caps.dvbsCaps().innerfecCap; + jint standard = caps.dvbsCaps().standard; + + return env->NewObject(clazz, capsInit, modulationCap, innerfecCap, standard); +} + +jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbtFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIZZ)V"); + + jint transmissionModeCap = caps.dvbtCaps().transmissionModeCap; + jint bandwidthCap = caps.dvbtCaps().bandwidthCap; + jint constellationCap = caps.dvbtCaps().constellationCap; + jint coderateCap = caps.dvbtCaps().coderateCap; + jint hierarchyCap = caps.dvbtCaps().hierarchyCap; + jint guardIntervalCap = caps.dvbtCaps().guardIntervalCap; + jboolean isT2Supported = caps.dvbtCaps().isT2Supported; + jboolean isMisoSupported = caps.dvbtCaps().isMisoSupported; + + return env->NewObject(clazz, capsInit, transmissionModeCap, bandwidthCap, constellationCap, + coderateCap, hierarchyCap, guardIntervalCap, isT2Supported, isMisoSupported); +} + +jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint modulationCap = caps.isdbs3Caps().modulationCap; + jint coderateCap = caps.isdbs3Caps().coderateCap; + + return env->NewObject(clazz, capsInit, modulationCap, coderateCap); +} + +jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbsFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint modulationCap = caps.isdbsCaps().modulationCap; + jint coderateCap = caps.isdbsCaps().coderateCap; + + return env->NewObject(clazz, capsInit, modulationCap, coderateCap); +} + +jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbtFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIII)V"); + + jint modeCap = caps.isdbtCaps().modeCap; + jint bandwidthCap = caps.isdbtCaps().bandwidthCap; + jint modulationCap = caps.isdbtCaps().modulationCap; + jint coderateCap = caps.isdbtCaps().coderateCap; + jint guardIntervalCap = caps.isdbtCaps().guardIntervalCap; + + return env->NewObject(clazz, capsInit, modeCap, bandwidthCap, modulationCap, coderateCap, + guardIntervalCap); +} + +jobject JTuner::getFrontendInfo(int id) { + FrontendInfo feInfo; + Result res; + mTuner->getFrontendInfo(id, [&](Result r, const FrontendInfo& info) { + feInfo = info; + res = r; + }); + if (res != Result::SUCCESS) { + return NULL; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendInfo"); + jmethodID infoInit = env->GetMethodID(clazz, "<init>", + "(IIIIIIII[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V"); + + jint type = (jint) feInfo.type; + jint minFrequency = feInfo.minFrequency; + jint maxFrequency = feInfo.maxFrequency; + jint minSymbolRate = feInfo.minSymbolRate; + jint maxSymbolRate = feInfo.maxSymbolRate; + jint acquireRange = feInfo.acquireRange; + jint exclusiveGroupId = feInfo.exclusiveGroupId; + jintArray statusCaps = env->NewIntArray(feInfo.statusCaps.size()); + env->SetIntArrayRegion( + statusCaps, 0, feInfo.statusCaps.size(), + reinterpret_cast<jint*>(&feInfo.statusCaps[0])); + FrontendInfo::FrontendCapabilities caps = feInfo.frontendCaps; + + jobject jcaps = NULL; + switch(feInfo.type) { + case FrontendType::ANALOG: + jcaps = getAnalogFrontendCaps(env, caps); + break; + case FrontendType::ATSC3: + jcaps = getAtsc3FrontendCaps(env, caps); + break; + case FrontendType::ATSC: + jcaps = getAtscFrontendCaps(env, caps); + break; + case FrontendType::DVBC: + jcaps = getDvbcFrontendCaps(env, caps); + break; + case FrontendType::DVBS: + jcaps = getDvbsFrontendCaps(env, caps); + break; + case FrontendType::DVBT: + jcaps = getDvbtFrontendCaps(env, caps); + break; + case FrontendType::ISDBS: + jcaps = getIsdbsFrontendCaps(env, caps); + break; + case FrontendType::ISDBS3: + jcaps = getIsdbs3FrontendCaps(env, caps); + break; + case FrontendType::ISDBT: + jcaps = getIsdbtFrontendCaps(env, caps); + break; + default: + break; + } + + return env->NewObject( + clazz, infoInit, (jint) id, type, minFrequency, maxFrequency, minSymbolRate, + maxSymbolRate, acquireRange, exclusiveGroupId, statusCaps, jcaps); +} + jobject JTuner::getLnbIds() { ALOGD("JTuner::getLnbIds()"); mTuner->getLnbIds([&](Result, const hidl_vec<FrontendId>& lnbIds) { @@ -377,6 +550,15 @@ int JTuner::tune(const FrontendSettings& settings) { return (int)result; } +int JTuner::stopTune() { + if (mFe == NULL) { + ALOGE("frontend is not initialized"); + return (int)Result::INVALID_STATE; + } + Result result = mFe->stopTune(); + return (int)result; +} + int JTuner::scan(const FrontendSettings& settings, FrontendScanType scanType) { if (mFe == NULL) { ALOGE("frontend is not initialized"); @@ -386,6 +568,33 @@ int JTuner::scan(const FrontendSettings& settings, FrontendScanType scanType) { return (int)result; } +int JTuner::stopScan() { + if (mFe == NULL) { + ALOGE("frontend is not initialized"); + return (int)Result::INVALID_STATE; + } + Result result = mFe->stopScan(); + return (int)result; +} + +int JTuner::setLnb(int id) { + if (mFe == NULL) { + ALOGE("frontend is not initialized"); + return (int)Result::INVALID_STATE; + } + Result result = mFe->setLnb(id); + return (int)result; +} + +int JTuner::setLna(bool enable) { + if (mFe == NULL) { + ALOGE("frontend is not initialized"); + return (int)Result::INVALID_STATE; + } + Result result = mFe->setLna(enable); + return (int)result; +} + bool JTuner::openDemux() { if (mTuner == nullptr) { return false; @@ -1079,8 +1288,9 @@ static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, job return tuner->tune(getFrontendSettings(env, type, settings)); } -static int android_media_tv_Tuner_stop_tune(JNIEnv*, jobject) { - return 0; +static int android_media_tv_Tuner_stop_tune(JNIEnv *env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->stopTune(); } static int android_media_tv_Tuner_scan( @@ -1090,16 +1300,19 @@ static int android_media_tv_Tuner_scan( env, settingsType, settings), static_cast<FrontendScanType>(scanType)); } -static int android_media_tv_Tuner_stop_scan(JNIEnv*, jobject) { - return 0; +static int android_media_tv_Tuner_stop_scan(JNIEnv *env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->stopScan(); } -static int android_media_tv_Tuner_set_lnb(JNIEnv*, jobject, jint) { - return 0; +static int android_media_tv_Tuner_set_lnb(JNIEnv *env, jobject thiz, jint id) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->setLnb(id); } -static int android_media_tv_Tuner_set_lna(JNIEnv*, jobject, jint, jboolean) { - return 0; +static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->setLna(enable); } static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) { @@ -1122,8 +1335,9 @@ static int android_media_tv_Tuner_disconnect_cicam(JNIEnv*, jobject) { return 0; } -static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv*, jobject, jint) { - return NULL; +static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getFrontendInfo(id); } static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 5c012bbebb9d..cfe99b399979 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -39,6 +39,7 @@ using ::android::hardware::tv::tuner::V1_0::DemuxPid; using ::android::hardware::tv::tuner::V1_0::DvrType; using ::android::hardware::tv::tuner::V1_0::FrontendEventType; using ::android::hardware::tv::tuner::V1_0::FrontendId; +using ::android::hardware::tv::tuner::V1_0::FrontendInfo; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType; using ::android::hardware::tv::tuner::V1_0::FrontendScanType; @@ -132,8 +133,13 @@ struct JTuner : public RefBase { sp<ITuner> getTunerService(); jobject getFrontendIds(); jobject openFrontendById(int id); + jobject getFrontendInfo(int id); int tune(const FrontendSettings& settings); + int stopTune(); int scan(const FrontendSettings& settings, FrontendScanType scanType); + int stopScan(); + int setLnb(int id); + int setLna(bool enable); jobject getLnbIds(); jobject openLnbById(int id); jobject openFilter(DemuxFilterType type, int bufferSize); @@ -154,6 +160,15 @@ private: sp<ILnb> mLnb; sp<IDemux> mDemux; int mDemuxId; + static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); }; } // namespace android diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 4316e42bb0e1..f10e5ebb5d3e 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -289,12 +289,13 @@ public class MediaRouterManagerTest { MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); - int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1); + int targetVolume = originalVolume == volRoute.getVolumeMax() + ? originalVolume - 1 : originalVolume + 1; awaitOnRouteChangedManager( - () -> mManager.requestUpdateVolume(volRoute, deltaVolume), + () -> mManager.requestSetVolume(volRoute, targetVolume), ROUTE_ID_VARIABLE_VOLUME, - (route -> route.getVolume() == originalVolume + deltaVolume)); + (route -> route.getVolume() == targetVolume)); awaitOnRouteChangedManager( () -> mManager.requestSetVolume(volRoute, originalVolume), diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java index e29b3239fcaa..1a866cafff90 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java @@ -154,21 +154,6 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onUpdateVolume(String routeId, int delta) { - android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta); - MediaRoute2Info route = mRoutes.get(routeId); - if (route == null) { - return; - } - int volume = route.getVolume() + delta; - volume = Math.min(volume, Math.max(0, route.getVolumeMax())); - mRoutes.put(routeId, new MediaRoute2Info.Builder(route) - .setVolume(volume) - .build()); - publishRoutes(); - } - - @Override public void onCreateSession(String packageName, String routeId, long requestId, @Nullable Bundle sessionHints) { MediaRoute2Info route = mRoutes.get(routeId); diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 392c9f6404ba..ba793e83f1fb 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -294,7 +294,7 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats; - for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) { + for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) { ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime; aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence; diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml index 9bd5be7b0dd7..7595d2b1eea3 100644 --- a/packages/DynamicSystemInstallationService/res/values/strings.xml +++ b/packages/DynamicSystemInstallationService/res/values/strings.xml @@ -35,4 +35,7 @@ <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] --> <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string> + <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] --> + <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string> + </resources> diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 9ccb837cf613..9bae223a0a3e 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -46,6 +46,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.http.HttpResponseCache; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -60,6 +61,8 @@ import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -146,10 +149,26 @@ public class DynamicSystemInstallationService extends Service prepareNotification(); mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); + + // Install an HttpResponseCache in the application cache directory so we can cache + // gsi key revocation list. The http(s) protocol handler uses this cache transparently. + // The cache size is chosen heuristically. Since we don't have too much traffic right now, + // a moderate size of 1MiB should be enough. + try { + File httpCacheDir = new File(getCacheDir(), "httpCache"); + long httpCacheSize = 1 * 1024 * 1024; // 1 MiB + HttpResponseCache.install(httpCacheDir, httpCacheSize); + } catch (IOException e) { + Log.d(TAG, "HttpResponseCache.install() failed: " + e); + } } @Override public void onDestroy() { + HttpResponseCache cache = HttpResponseCache.getInstalled(); + if (cache != null) { + cache.flush(); + } // Cancel the persistent notification. mNM.cancel(NOTIFICATION_ID); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 9aea0e713179..438c435ef0e4 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -25,6 +25,8 @@ import android.os.image.DynamicSystemManager; import android.util.Log; import android.webkit.URLUtil; +import org.json.JSONException; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -100,7 +102,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog private final Context mContext; private final DynamicSystemManager mDynSystem; private final ProgressListener mListener; + private final boolean mIsNetworkUrl; private DynamicSystemManager.Session mInstallationSession; + private KeyRevocationList mKeyRevocationList; private boolean mIsZip; private boolean mIsCompleted; @@ -123,6 +127,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog mContext = context; mDynSystem = dynSystem; mListener = listener; + mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); } @Override @@ -152,9 +157,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return null; } + // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk) + mDynSystem.finishInstallation(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, e.toString(), e); mDynSystem.remove(); return e; } finally { @@ -220,7 +227,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog String.format(Locale.US, "Unsupported file format: %s", mUrl)); } - if (URLUtil.isNetworkUrl(mUrl)) { + if (mIsNetworkUrl) { mStream = new URL(mUrl).openStream(); } else if (URLUtil.isFileUrl(mUrl)) { if (mIsZip) { @@ -234,6 +241,25 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog throw new UnsupportedUrlException( String.format(Locale.US, "Unsupported URL: %s", mUrl)); } + + // TODO(yochiang): Bypass this check if device is unlocked + try { + String listUrl = mContext.getString(R.string.key_revocation_list_url); + mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl)); + } catch (IOException | JSONException e) { + Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List"); + mKeyRevocationList = new KeyRevocationList(); + keyRevocationThrowOrWarning(e); + } + } + + private void keyRevocationThrowOrWarning(Exception e) throws Exception { + if (mIsNetworkUrl) { + throw e; + } else { + // If DSU is being installed from a local file URI, then be permissive + Log.w(TAG, e.toString()); + } } private void installUserdata() throws Exception { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java new file mode 100644 index 000000000000..522bc547325b --- /dev/null +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java @@ -0,0 +1,148 @@ +/* + * 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 com.android.dynsystem; + +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; + +class KeyRevocationList { + + private static final String TAG = "KeyRevocationList"; + + private static final String JSON_ENTRIES = "entries"; + private static final String JSON_PUBLIC_KEY = "public_key"; + private static final String JSON_STATUS = "status"; + private static final String JSON_REASON = "reason"; + + private static final String STATUS_REVOKED = "REVOKED"; + + @VisibleForTesting + HashMap<String, RevocationStatus> mEntries; + + static class RevocationStatus { + final String mStatus; + final String mReason; + + RevocationStatus(String status, String reason) { + mStatus = status; + mReason = reason; + } + } + + KeyRevocationList() { + mEntries = new HashMap<String, RevocationStatus>(); + } + + /** + * Returns the revocation status of a public key. + * + * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist. + */ + RevocationStatus getRevocationStatusForKey(String publicKey) { + return mEntries.get(publicKey); + } + + /** Test if a public key is revoked or not. */ + boolean isRevoked(String publicKey) { + RevocationStatus entry = getRevocationStatusForKey(publicKey); + return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED); + } + + @VisibleForTesting + void addEntry(String publicKey, String status, String reason) { + mEntries.put(publicKey, new RevocationStatus(status, reason)); + } + + /** + * Creates a KeyRevocationList from a JSON String. + * + * @param jsonString the revocation list, for example: + * <pre>{@code + * { + * "entries": [ + * { + * "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5", + * "status": "REVOKED", + * "reason": "Revocation Reason" + * } + * ] + * } + * }</pre> + * + * @throws JSONException if |jsonString| is malformed. + */ + static KeyRevocationList fromJsonString(String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + KeyRevocationList list = new KeyRevocationList(); + Log.d(TAG, "Begin of revocation list"); + if (jsonObject.has(JSON_ENTRIES)) { + JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES); + for (int i = 0; i < entries.length(); ++i) { + JSONObject entry = entries.getJSONObject(i); + String publicKey = entry.getString(JSON_PUBLIC_KEY); + String status = entry.getString(JSON_STATUS); + String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : ""; + list.addEntry(publicKey, status, reason); + Log.d(TAG, "Revocation entry: " + entry.toString()); + } + } + Log.d(TAG, "End of revocation list"); + return list; + } + + /** + * Creates a KeyRevocationList from a URL. + * + * @throws IOException if |url| is inaccessible. + * @throws JSONException if fetched content is malformed. + */ + static KeyRevocationList fromUrl(URL url) throws IOException, JSONException { + Log.d(TAG, "Fetch from URL: " + url.toString()); + // Force "conditional GET" + // Force validate the cached result with server each time, and use the cached result + // only if it is validated by server, else fetch new data from server. + // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response + URLConnection connection = url.openConnection(); + connection.setUseCaches(true); + connection.addRequestProperty("Cache-Control", "max-age=0"); + try (InputStream stream = connection.getInputStream()) { + return fromJsonString(readFully(stream)); + } + } + + private static String readFully(InputStream in) throws IOException { + int n; + byte[] buffer = new byte[4096]; + StringBuilder builder = new StringBuilder(); + while ((n = in.read(buffer, 0, 4096)) > -1) { + builder.append(new String(buffer, 0, n)); + } + return builder.toString(); + } +} diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp new file mode 100644 index 000000000000..3bdf82966889 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/Android.bp @@ -0,0 +1,15 @@ +android_test { + name: "DynamicSystemInstallationServiceTests", + + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.runner", + "androidx.test.rules", + "mockito-target-minus-junit4", + ], + + resource_dirs: ["res"], + platform_apis: true, + instrumentation_for: "DynamicSystemInstallationService", + certificate: "platform", +} diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml new file mode 100644 index 000000000000..f5f0ae6adaba --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dynsystem.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.dynsystem" + android:label="Tests for DynamicSystemInstallationService" /> + +</manifest> diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml new file mode 100644 index 000000000000..fdb620bfe094 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- testFromJsonString --> + <string name="blacklist_json_string" translatable="false"> + { + \"entries\":[ + { + \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\", + \"status\":\"REVOKED\", + \"reason\":\"Key revocation test key\" + }, + { + \"public_key\":\"key2\", + \"status\":\"REVOKED\" + } + ] + } + </string> +</resources> diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java new file mode 100644 index 000000000000..82ce542cf5de --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java @@ -0,0 +1,132 @@ +/* + * 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 com.android.dynsystem; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A test for KeyRevocationList.java + */ +@RunWith(AndroidJUnit4.class) +public class KeyRevocationListTest { + + private static final String TAG = "KeyRevocationListTest"; + + private static Context sContext; + + private static String sBlacklistJsonString; + + @BeforeClass + public static void setUpClass() throws Exception { + sContext = InstrumentationRegistry.getInstrumentation().getContext(); + sBlacklistJsonString = + sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string); + } + + @Test + @SmallTest + public void testFromJsonString() throws JSONException { + KeyRevocationList blacklist; + blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + blacklist = KeyRevocationList.fromJsonString("{}"); + Assert.assertNotNull(blacklist); + Assert.assertTrue(blacklist.mEntries.isEmpty()); + } + + @Test + @SmallTest + public void testFromUrl() throws IOException, JSONException { + URLConnection mockConnection = mock(URLConnection.class); + doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes())) + .when(mockConnection).getInputStream(); + URL mockUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) { + return mockConnection; + } + }); + URL mockBadUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException { + throw new IOException(); + } + }); + + KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + + blacklist = null; + try { + blacklist = KeyRevocationList.fromUrl(mockBadUrl); + // Up should throw, down should be unreachable + Assert.fail("Expected IOException not thrown"); + } catch (IOException e) { + // This is expected, do nothing + } + Assert.assertNull(blacklist); + } + + @Test + @SmallTest + public void testIsRevoked() { + KeyRevocationList blacklist = new KeyRevocationList(); + blacklist.addEntry("key1", "REVOKED", "reason for key1"); + + KeyRevocationList.RevocationStatus revocationStatus = + blacklist.getRevocationStatusForKey("key1"); + Assert.assertNotNull(revocationStatus); + Assert.assertEquals(revocationStatus.mReason, "reason for key1"); + + revocationStatus = blacklist.getRevocationStatusForKey("key2"); + Assert.assertNull(revocationStatus); + + Assert.assertTrue(blacklist.isRevoked("key1")); + Assert.assertFalse(blacklist.isRevoked("key2")); + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 9c8345dafbc8..62124934f416 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -10,6 +10,7 @@ android_library { "androidx.appcompat_appcompat", "androidx.lifecycle_lifecycle-runtime", "androidx.mediarouter_mediarouter-nodeps", + "iconloader", "SettingsLibHelpUtils", "SettingsLibRestrictedLockUtils", diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml new file mode 100644 index 000000000000..332d6c7bc0fa --- /dev/null +++ b/packages/SettingsLib/res/values/config.xml @@ -0,0 +1,24 @@ +<?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. + --> +<!-- These resources are around just to allow their values to be customized --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> + <integer name="config_chargingSlowlyThreshold">5000000</integer> + + <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> + <integer name="config_chargingFastThreshold">7500000</integer> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index dd21e5c07b13..2e7a3c0eb263 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1034,8 +1034,10 @@ <string name="battery_info_status_unknown">Unknown</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. --> <string name="battery_info_status_charging">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen with lower case. Battery status shown in chart label when charging from an unknown source. --> - <string name="battery_info_status_charging_lower">charging</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is fast. --> + <string name="battery_info_status_charging_fast">Charging rapidly</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is slow. --> + <string name="battery_info_status_charging_slow">Charging slowly</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index de523d9f9bc8..f4857932064f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -3,6 +3,7 @@ package com.android.settingslib; import android.annotation.ColorInt; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -13,6 +14,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.media.AudioManager; @@ -27,9 +29,13 @@ import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; +import com.android.launcher3.icons.IconFactory; import com.android.settingslib.drawable.UserIconDrawable; +import com.android.settingslib.fuelgauge.BatteryStatus; import java.text.NumberFormat; @@ -117,7 +123,7 @@ public class Utils { public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { final int iconSize = UserIconDrawable.getSizeForList(context); if (user.isManagedProfile()) { - Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); + Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); drawable.setBounds(0, 0, iconSize, iconSize); return drawable; } @@ -159,20 +165,43 @@ public class Utils { return (level * 100) / scale; } - public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { - int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, + /** + * Get battery status string + * + * @param context the context + * @param batteryChangedIntent battery broadcast intent received from {@link + * Intent.ACTION_BATTERY_CHANGED}. + * @return battery status string + */ + public static String getBatteryStatus(Context context, Intent batteryChangedIntent) { + final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); - String statusString; - if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - statusString = res.getString(R.string.battery_info_status_charging); - } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { - statusString = res.getString(R.string.battery_info_status_discharging); - } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { - statusString = res.getString(R.string.battery_info_status_not_charging); - } else if (status == BatteryManager.BATTERY_STATUS_FULL) { + final Resources res = context.getResources(); + + String statusString = res.getString(R.string.battery_info_status_unknown); + final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent); + + if (batteryStatus.isCharged()) { statusString = res.getString(R.string.battery_info_status_full); } else { - statusString = res.getString(R.string.battery_info_status_unknown); + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + switch (batteryStatus.getChargingSpeed(context)) { + case BatteryStatus.CHARGING_FAST: + statusString = res.getString(R.string.battery_info_status_charging_fast); + break; + case BatteryStatus.CHARGING_SLOWLY: + statusString = res.getString(R.string.battery_info_status_charging_slow); + break; + default: + statusString = res.getString(R.string.battery_info_status_charging); + break; + } + + } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { + statusString = res.getString(R.string.battery_info_status_discharging); + } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { + statusString = res.getString(R.string.battery_info_status_not_charging); + } } return statusString; @@ -206,7 +235,7 @@ public class Utils { /** * This method computes disabled color from normal color * - * @param context + * @param context the context * @param inputColor normal color. * @return disabled color. */ @@ -424,6 +453,19 @@ public class Utils { return state; } + /** + * Get the {@link Drawable} that represents the app icon + */ + public static @NonNull Drawable getBadgedIcon( + @NonNull Context context, @NonNull ApplicationInfo appInfo) { + final UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); + try (IconFactory iconFactory = IconFactory.obtain(context)) { + final Bitmap iconBmp = iconFactory.createBadgedIconBitmap( + appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; + return new BitmapDrawable(context.getResources(), iconBmp); + } + } + private static boolean isNotInIwlan(ServiceState serviceState) { final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 19c666459723..af728887c917 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -59,6 +59,7 @@ import androidx.lifecycle.OnLifecycleEvent; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import com.android.settingslib.Utils; import java.io.File; import java.io.IOException; @@ -495,7 +496,7 @@ public class ApplicationsState { return; } synchronized (entry) { - entry.ensureIconLocked(mContext, mDrawableFactory); + entry.ensureIconLocked(mContext); } } @@ -1216,7 +1217,7 @@ public class ApplicationsState { AppEntry entry = mAppEntries.get(i); if (entry.icon == null || !entry.mounted) { synchronized (entry) { - if (entry.ensureIconLocked(mContext, mDrawableFactory)) { + if (entry.ensureIconLocked(mContext)) { if (!mRunning) { mRunning = true; Message m = mMainHandler.obtainMessage( @@ -1587,10 +1588,10 @@ public class ApplicationsState { } } - boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) { + boolean ensureIconLocked(Context context) { if (this.icon == null) { if (this.apkFile.exists()) { - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } else { this.mounted = false; @@ -1601,7 +1602,7 @@ public class ApplicationsState { // its icon. if (this.apkFile.exists()) { this.mounted = true; - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index ddb7341b7366..1ebe91736ba1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -153,21 +153,6 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -187,31 +172,37 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getActiveDevice(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isA2dpPlaying() { if (mService == null) return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index 8ca5a74652dc..c7a5bd86c1cd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -115,21 +115,6 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -137,31 +122,37 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isAudioPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 50d3a5daeb84..3aa35cb48c38 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -195,7 +195,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (profile instanceof MapProfile) { - profile.setPreferred(mDevice, true); + profile.setEnabled(mDevice, true); } if (!mProfiles.contains(profile)) { mRemovedProfiles.remove(profile); @@ -208,7 +208,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } else if (profile instanceof MapProfile && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { - profile.setPreferred(mDevice, false); + profile.setEnabled(mDevice, false); } else if (mLocalNapRoleConnected && profile instanceof PanProfile && ((PanProfile) profile).isLocalRoleNap(mDevice) && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { @@ -250,12 +250,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); if (PbapProfile != null && isConnectedProfile(PbapProfile)) { - PbapProfile.disconnect(mDevice); + PbapProfile.setEnabled(mDevice, false); } } public void disconnect(LocalBluetoothProfile profile) { - if (profile.disconnect(mDevice)) { + if (profile.setEnabled(mDevice, false)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); } @@ -342,7 +342,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (!ensurePaired()) { return; } - if (profile.connect(mDevice)) { + if (profile.setEnabled(mDevice, true)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 218d0b2dc2c0..9dfc4d986745 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -114,21 +114,6 @@ public class HeadsetProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -164,31 +149,37 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.getAudioState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index b82fb37a770f..a3b68b4b90b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -151,21 +151,6 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -185,31 +170,37 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getActiveDevices(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public void setVolume(int volume) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 678f2e37c6bf..66225a2bffca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -125,23 +125,6 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,7 +133,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } @@ -158,7 +141,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } @@ -166,17 +149,20 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index 35600b538d4d..8a2c4f8a1230 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -102,20 +104,6 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - // Don't invoke method in service because settings is not allowed to connect this profile. - return false; - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.disconnect(device); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -124,21 +112,24 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { return getConnectionStatus(device) != BluetoothProfile.STATE_DISCONNECTED; } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { return PREFERRED_VALUE; } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; // if set preferred to false, then disconnect to the current device - if (!preferred) { - mService.disconnect(device); + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 588083e73481..3c24b4a095b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -101,20 +101,6 @@ public class HidProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -122,29 +108,37 @@ public class HidProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (mService == null) return; - if (preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java index 4b0ca7434f9a..f609e4311082 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -35,17 +35,26 @@ public interface LocalBluetoothProfile { */ boolean isAutoConnectable(); - boolean connect(BluetoothDevice device); - - boolean disconnect(BluetoothDevice device); - int getConnectionStatus(BluetoothDevice device); - boolean isPreferred(BluetoothDevice device); + /** + * Return {@code true} if the profile is enabled, otherwise return {@code false}. + * @param device the device to query for enable status + */ + boolean isEnabled(BluetoothDevice device); - int getPreferred(BluetoothDevice device); + /** + * Get the connection policy of the profile. + * @param device the device to query for enable status + */ + int getConnectionPolicy(BluetoothDevice device); - void setPreferred(BluetoothDevice device, boolean preferred); + /** + * Enable the profile if {@code enabled} is {@code true}, otherwise disable profile. + * @param device the device to set profile status + * @param enabled {@code true} for enable profile, otherwise disable profile. + */ + boolean setEnabled(BluetoothDevice device, boolean enabled); boolean isProfileReady(); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index ae2acbea8e4d..c72efb7eec83 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -528,14 +528,14 @@ public class LocalBluetoothProfileManager { (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mMapProfile); removedProfiles.remove(mMapProfile); - mMapProfile.setPreferred(device, true); + mMapProfile.setEnabled(device, true); } if ((mPbapProfile != null) && (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mPbapProfile); removedProfiles.remove(mPbapProfile); - mPbapProfile.setPreferred(device, true); + mPbapProfile.setEnabled(device, true); } if (mMapClientProfile != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 7d121aaa1ad1..19cb2f59f321 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -114,21 +114,6 @@ public final class MapClientProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -136,31 +121,37 @@ public final class MapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index a96a4e73feea..75c1926683ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.bluetooth.BluetoothAdapter; @@ -112,19 +113,6 @@ public class MapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - Log.d(TAG, "connect() - should not get called"); - return false; // MAP never connects out - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -132,31 +120,37 @@ public class MapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (enabled) { + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java index 8e3f3edcef10..5a6e6e8b830d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -40,27 +40,23 @@ final class OppProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - return false; - } - - public boolean disconnect(BluetoothDevice device) { - return false; - } - public int getConnectionStatus(BluetoothDevice device) { return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + return false; } public boolean isProfileReady() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 6638592e8be5..767df352b70f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -83,22 +86,6 @@ public class PanProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - List<BluetoothDevice> sinks = mService.getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -106,16 +93,36 @@ public class PanProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return true; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PAN + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (enabled) { + final List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN); + } + } + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + } else { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 56267fc596cf..0d11293a01b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -126,23 +126,6 @@ public final class PbapClientProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got connect request"); - if (mService == null) { - return false; - } - Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got disconnect request"); - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,31 +133,37 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index f7c0bf5c8c9d..9e2e4a14124a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -91,34 +91,33 @@ public class PbapServerProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - /*Can't connect from server */ - return false; - - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PBAP + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 3022c5b566eb..104f1d738000 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -111,21 +111,6 @@ final class SapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -133,31 +118,37 @@ final class SapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java new file mode 100644 index 000000000000..bc40903d88e4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -0,0 +1,147 @@ +/* + * 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 com.android.settingslib.fuelgauge; + +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.BatteryManager.EXTRA_HEALTH; +import static android.os.BatteryManager.EXTRA_LEVEL; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; +import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.BatteryManager.EXTRA_STATUS; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settingslib.R; + +/** + * Stores and computes some battery information. + */ +public class BatteryStatus { + private static final int LOW_BATTERY_THRESHOLD = 20; + private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000; + + public static final int CHARGING_UNKNOWN = -1; + public static final int CHARGING_SLOWLY = 0; + public static final int CHARGING_REGULAR = 1; + public static final int CHARGING_FAST = 2; + + public final int status; + public final int level; + public final int plugged; + public final int health; + public final int maxChargingWattage; + + public BatteryStatus(int status, int level, int plugged, int health, + int maxChargingWattage) { + this.status = status; + this.level = level; + this.plugged = plugged; + this.health = health; + this.maxChargingWattage = maxChargingWattage; + } + + public BatteryStatus(Intent batteryChangedIntent) { + status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); + plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0); + level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0); + health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + + final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, + -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + if (maxChargingMicroVolt <= 0) { + maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; + } + if (maxChargingMicroAmp > 0) { + // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor + // to maintain precision equally on both factors. + maxChargingWattage = (maxChargingMicroAmp / 1000) + * (maxChargingMicroVolt / 1000); + } else { + maxChargingWattage = -1; + } + } + + /** + * Determine whether the device is plugged in (USB, power, or wireless). + * + * @return true if the device is plugged in. + */ + public boolean isPluggedIn() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB + || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + + /** + * Determine whether the device is plugged in (USB, power). + * + * @return true if the device is plugged in wired (as opposed to wireless) + */ + public boolean isPluggedInWired() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + /** + * Whether or not the device is charged. Note that some devices never return 100% for + * battery level, so this allows either battery level or status to determine if the + * battery is charged. + * + * @return true if the device is charged + */ + public boolean isCharged() { + return status == BATTERY_STATUS_FULL || level >= 100; + } + + /** + * Whether battery is low and needs to be charged. + * + * @return true if battery is low + */ + public boolean isBatteryLow() { + return level < LOW_BATTERY_THRESHOLD; + } + + /** + * Return current chargin speed is fast, slow or normal. + * + * @return the charing speed + */ + public final int getChargingSpeed(Context context) { + final int slowThreshold = context.getResources().getInteger( + R.integer.config_chargingSlowlyThreshold); + final int fastThreshold = context.getResources().getInteger( + R.integer.config_chargingFastThreshold); + return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : + maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : + maxChargingWattage > fastThreshold ? CHARGING_FAST : + CHARGING_REGULAR; + } + + @Override + public String toString() { + return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged + + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java index 12d054e307ba..3a807c9b9b77 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java @@ -108,9 +108,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + a2dpProfile.isPreferred(device)); + + ", is enabled : " + a2dpProfile.isEnabled(device)); - if (a2dpProfile.isPreferred(device) + if (a2dpProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } @@ -143,9 +143,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + hapProfile.isPreferred(device)); + + ", is enabled : " + hapProfile.isEnabled(device)); - if (hapProfile.isPreferred(device) + if (hapProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index d287f95e504a..ba741396a6bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,9 +16,12 @@ 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; @@ -89,6 +92,31 @@ public class InfoMediaDevice extends MediaDevice { } @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); + } + + @Override public void disconnect() { //TODO(b/144535188): disconnected last select device } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 50196d2b2994..96d72e755026 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -235,6 +235,22 @@ public class LocalMediaManager implements BluetoothCallback { return mCurrentConnectedDevice; } + /** + * Find the active MediaDevice. + * + * @param type the media device type. + * @return MediaDevice list + */ + public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) { + final List<MediaDevice> devices = new ArrayList<>(); + for (MediaDevice device : mMediaDevices) { + if (type == device.mType && device.getClientPackageName() != null) { + devices.add(device); + } + } + return devices; + } + private MediaDevice updateCurrentConnectedDevice() { for (MediaDevice device : mMediaDevices) { if (device instanceof BluetoothMediaDevice) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 44e70f436963..bfb79c05a432 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -16,6 +16,9 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED; + import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.Nullable; @@ -1144,11 +1147,15 @@ public class AccessPoint implements Comparable<AccessPoint> { mInfo != null ? mInfo.getRequestingPackageName() : null)); } else { // not active if (mConfig != null && mConfig.hasNoInternetAccess()) { - int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled() + int messageID = + mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + == NETWORK_SELECTION_PERMANENTLY_DISABLED ? R.string.wifi_no_internet_no_reconnect : R.string.wifi_no_internet; summary.append(mContext.getString(messageID)); - } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) { + } else if (mConfig != null + && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { WifiConfiguration.NetworkSelectionStatus networkStatus = mConfig.getNetworkSelectionStatus(); switch (networkStatus.getNetworkSelectionDisableReason()) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index d4e0510e15a7..d23364952357 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -16,9 +16,13 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason; + import android.content.Context; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.net.wifi.WifiInfo; import android.os.SystemClock; @@ -41,7 +45,9 @@ public class WifiUtils { summary.append(" f=" + Integer.toString(info.getFrequency())); } summary.append(" " + getVisibilityStatus(accessPoint)); - if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) { + if (config != null + && (config.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); if (config.getNetworkSelectionStatus().getDisableTime() > 0) { long now = System.currentTimeMillis(); @@ -58,15 +64,13 @@ public class WifiUtils { } if (config != null) { - WifiConfiguration.NetworkSelectionStatus networkStatus = - config.getNetworkSelectionStatus(); - for (int index = WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE; - index < WifiConfiguration.NetworkSelectionStatus - .NETWORK_SELECTION_DISABLED_MAX; index++) { - if (networkStatus.getDisableReasonCounter(index) != 0) { - summary.append(" " + WifiConfiguration.NetworkSelectionStatus - .getNetworkDisableReasonString(index) + "=" - + networkStatus.getDisableReasonCounter(index)); + NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); + for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) { + if (networkStatus.getDisableReasonCounter(reason) != 0) { + summary.append(" ") + .append(NetworkSelectionStatus.getNetworkDisableReasonString(reason)) + .append("=") + .append(networkStatus.getDisableReasonCounter(reason)); } } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 42f3cbb04cf8..bcabec858487 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -465,6 +465,8 @@ public class AccessPointTest { WifiConfiguration.NetworkSelectionStatus status = mock(WifiConfiguration.NetworkSelectionStatus.class); when(configuration.getNetworkSelectionStatus()).thenReturn(status); + when(status.getNetworkSelectionStatus()).thenReturn( + WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED); when(status.getNetworkSelectionDisableReason()).thenReturn( WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD); AccessPoint ap = new AccessPoint(mContext, configuration); @@ -1370,13 +1372,13 @@ public class AccessPointTest { public void testOsuAccessPointSummary_showsProvisioningUpdates() { OsuProvider provider = createOsuProvider(); Context spyContext = spy(new ContextWrapper(mContext)); - AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, - mScanResults); + when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); osuProviderConfigMap.put(provider, null); - when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders( Collections.singleton(provider))).thenReturn(osuProviderConfigMap); + AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, + mScanResults); osuAccessPoint.setListener(mMockAccessPointListener); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 11829451f640..6307caf5e02b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.res.Resources; import android.location.LocationManager; import android.media.AudioManager; +import android.os.BatteryManager; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; @@ -122,12 +123,12 @@ public class UtilsTest { public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() { Resources resources = mock(Resources.class); when(resources.getInteger( - eq( - com.android - .internal - .R - .integer - .config_storageManagerDaystoRetainDefault))) + eq( + com.android + .internal + .R + .integer + .config_storageManagerDaystoRetainDefault))) .thenReturn(60); assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60); } @@ -147,7 +148,8 @@ public class UtilsTest { private static Map<String, Integer> map = new HashMap<>(); @Implementation - public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) { + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userHandle) { map.put(name, value); return true; } @@ -312,4 +314,33 @@ public class UtilsTest { assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo( ServiceState.STATE_OUT_OF_SERVICE); } + + @Test + public void getBatteryStatus_statusIsFull_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevelIs100_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_FULL); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevel99_returnChargingString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_CHARGING); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_charging)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index ccb6646cf683..9bb2f22ddbcf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothA2dpSink; @@ -68,18 +64,6 @@ public class A2dpSinkProfileTest { } @Test - public void connect_shouldConnectBluetoothA2dpSink() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothA2dpSink() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 91807609df1a..d121e0b2d2fb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class HfpClientProfileTest { } @Test - public void connect_shouldConnectBluetoothHeadsetClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHeadsetClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java index f38af70c7498..3665d9c10165 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java @@ -18,7 +18,6 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -65,17 +64,6 @@ public class HidDeviceProfileTest { } @Test - public void connect_shouldReturnFalse() { - assertThat(mProfile.connect(mBluetoothDevice)).isFalse(); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHidDevice() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 1425c381256b..25031a62294c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class MapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothMapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothMapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index 15f560bef73e..4305a3bc25a3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class PbapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothPbapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothPbapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index 4f978a822890..e460eaf16bbf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -67,18 +63,6 @@ public class SapProfileTest { } @Test - public void connect_shouldConnectBluetoothSap() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothSap() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java index f27cef9620c5..0ee5ea8a2eed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java @@ -96,7 +96,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -113,7 +113,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -141,7 +141,7 @@ public class BluetoothMediaManagerTest { when(mHapProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mHapProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mHapProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); 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 c9db0d13a7e7..2e304dcd58aa 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 @@ -22,6 +22,9 @@ import static org.mockito.Mockito.verify; 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; @@ -34,11 +37,14 @@ 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"; @@ -50,11 +56,24 @@ public class InfoMediaDeviceTest { 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); @@ -95,4 +114,32 @@ public class InfoMediaDeviceTest { verify(mRouterManager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo); } + + @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/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index c780a64c2fb4..15aaa8219269 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -384,4 +384,38 @@ public class LocalMediaManagerTest { verify(mCallback).onDeviceAttributesChanged(); } + + @Test + public void getActiveMediaDevice_checkList() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice device1 = mock(MediaDevice.class); + final MediaDevice device2 = mock(MediaDevice.class); + final MediaDevice device3 = mock(MediaDevice.class); + device1.mType = MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE; + device2.mType = MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE; + device3.mType = MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE; + when(device1.getClientPackageName()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getClientPackageName()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getClientPackageName()).thenReturn(TEST_DEVICE_ID_3); + when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getId()).thenReturn(TEST_DEVICE_ID_3); + devices.add(device1); + devices.add(device2); + devices.add(device3); + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + List<MediaDevice> activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); + assertThat(activeDevices).containsExactly(device1); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + assertThat(activeDevices).containsExactly(device2); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + assertThat(activeDevices).containsExactly(device3); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 9934e59d8d56..cd62420f39ac 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -425,7 +425,8 @@ final class SettingsState { } newState = oldState; } else { - newState = new Setting(name, value, makeDefault, packageName, tag); + newState = new Setting(name, value, makeDefault, packageName, tag, + forceNonSystemPackage); mSettings.put(name, newState); } @@ -1173,11 +1174,15 @@ final class SettingsState { public Setting(String name, String value, boolean makeDefault, String packageName, String tag) { + this(name, value, makeDefault, packageName, tag, false); + } + + Setting(String name, String value, boolean makeDefault, String packageName, + String tag, boolean forceNonSystemPackage) { this.name = name; // overrideableByRestore = true as the first initialization isn't considered a // modification. - update(value, makeDefault, packageName, tag, false, - /* overrideableByRestore */ true); + update(value, makeDefault, packageName, tag, forceNonSystemPackage, true); } public Setting(String name, String value, String defaultValue, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index d67a9bc97cb8..8ff595b3bc53 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -33,7 +33,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; -import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -692,125 +691,4 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { cursor.close(); } } - - @Test - @Ignore("b/140250974") - public void testLocationModeChanges_viaFrontEndApi() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_BATTERY_SAVING), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "gps,network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_disableProviders() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - - // Disable providers that were enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-gps,-network"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - - // Disable a provider that was not enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-test"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_enableAndDisable() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps,+network,+test"); - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test"); - - assertEquals( - "Wrong location providers", - "gps,network", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowedLocked_invalidInput() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - // update providers with a invalid string - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps, invalid-string"); - - // Verifies providers list does not change - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 6a89b71be897..1d679c7bcbdd 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -223,6 +223,11 @@ <!-- permissions required for CTS test - PhoneStateListenerTest --> <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" /> + <!-- Permissions required for ganting and logging --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/> + <!-- Permission required for CTS test - UiModeManagerTest --> <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 139a8c3f7411..1fe967b4750d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -645,7 +645,6 @@ <activity android:name=".controls.management.ControlsProviderSelectorActivity" android:label="Controls Providers" android:theme="@style/Theme.ControlsManagement" - android:exported="true" android:showForAllUsers="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index bde6ed531353..8d9d6ee68c67 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,10 +22,4 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> - - <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> - <integer name="config_chargingSlowlyThreshold">5000000</integer> - - <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> - <integer name="config_chargingFastThreshold">7500000</integer> </resources> diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml new file mode 100644 index 000000000000..24e063506250 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_touch.xml b/packages/SystemUI/res/drawable/ic_touch.xml new file mode 100644 index 000000000000..4f6698de5251 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_touch.xml @@ -0,0 +1,26 @@ +<!-- +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. +--> +<!-- maybe need android:fillType="evenOdd" --> +<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="#FF000000" + android:pathData="M9,7.5V11.24C7.79,10.43 7,9.06 7,7.5C7,5.01 9.01,3 11.5,3C13.99,3 16,5.01 16,7.5C16,9.06 15.21,10.43 14,11.24V7.5C14,6.12 12.88,5 11.5,5C10.12,5 9,6.12 9,7.5ZM14.3,13.61L18.84,15.87C19.37,16.09 19.75,16.63 19.75,17.25C19.75,17.31 19.74,17.38 19.73,17.45L18.98,22.72C18.87,23.45 18.29,24 17.54,24H10.75C10.34,24 9.96,23.83 9.69,23.56L4.75,18.62L5.54,17.82C5.74,17.62 6.02,17.49 6.33,17.49C6.39,17.49 6.4411,17.4989 6.4922,17.5078C6.5178,17.5122 6.5433,17.5167 6.57,17.52L10,18.24V7.5C10,6.67 10.67,6 11.5,6C12.33,6 13,6.67 13,7.5V13.5H13.76C13.95,13.5 14.13,13.54 14.3,13.61Z" + /> +</vector> diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index a1c593fa455c..b14bc7de06c4 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -73,6 +73,7 @@ android:layout_width="208dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:minHeight="48dp" android:gravity="center" android:inputType="textPassword" android:maxLength="500" diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml new file mode 100644 index 000000000000..df576d83323f --- /dev/null +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -0,0 +1,142 @@ +<?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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@drawable/rounded_bg_full"> + + <!-- Header --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:padding="@dimen/screenrecord_dialog_padding"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_screenrecord" + android:tint="@color/GM2_red_500" + android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="@string/screenrecord_start_label"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screenrecord_description" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingTop="@dimen/screenrecord_dialog_padding" + android:paddingBottom="@dimen/screenrecord_dialog_padding"/> + + <!-- Options --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_mic_26dp" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_weight="0" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" + android:text="@string/screenrecord_audio_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/audio_type" + android:text="@string/screenrecord_mic_label" + android:textAppearance="?android:attr/textAppearanceSmall"/> + </LinearLayout> + <Switch + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_weight="0" + android:id="@+id/screenrecord_audio_switch"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_touch" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <Switch + android:layout_width="match_parent" + android:layout_height="48dp" + android:id="@+id/screenrecord_taps_switch" + android:text="@string/screenrecord_taps_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </LinearLayout> + </LinearLayout> + + <!-- hr --> + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/GM2_grey_300"/> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="@dimen/screenrecord_dialog_padding"> + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="start" + android:text="@string/cancel" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="end" + android:text="@string/screenrecord_start" + style="@android:style/Widget.DeviceDefault.Button.Colored"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9c997e88c0c2..c4fa4e5d4ef3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1090,7 +1090,7 @@ <!-- Blur radius on status bar window and power menu --> <dimen name="min_window_blur_radius">1px</dimen> - <dimen name="max_window_blur_radius">100px</dimen> + <dimen name="max_window_blur_radius">250px</dimen> <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> @@ -1209,5 +1209,7 @@ <dimen name="controls_card_margin">2dp</dimen> - + <!-- Screen Record --> + <dimen name="screenrecord_dialog_padding">18dp</dimen> + <dimen name="screenrecord_logo_size">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 82cd5f71e8a0..b85b51e4f2cc 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -217,15 +217,31 @@ your organization</string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> - <string name="screenrecord_name">Screen Recording</string> + <string name="screenrecord_name">Screen Recorder</string> <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]--> <string name="screenrecord_channel_description">Ongoing notification for a screen record session</string> - <!-- Label for the button to begin screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_start_label">Start Recording</string> - <!-- Label for the checkbox to enable microphone input during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_mic_label">Record voiceover</string> + <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]--> + <string name="screenrecord_start_label">Start Recording?</string> + <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]--> + <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string> + <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]--> + <string name="screenrecord_audio_label">Record audio</string> + <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_label">Device audio</string> + <!-- Description of what audio may be captured from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_description">Sound from your device, like music, calls, and ringtones</string> + <!-- Label for the option to enable microphone input during screen recording [CHAR LIMIT=NONE]--> + <string name="screenrecord_mic_label">Microphone</string> + <!-- Label for an option to record audio from both device and microphone [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_and_mic_label">Device audio and microphone</string> + <!-- Button to start a screen recording [CHAR LIMIT=50]--> + <string name="screenrecord_start">Start</string> + <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_only">Recording screen</string> + <!-- Notification text displayed when we are recording both the screen and audio [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_and_audio">Recording screen and audio</string> <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_taps_label">Show taps</string> + <string name="screenrecord_taps_label">Show touches on screen</string> <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] --> <string name="screenrecord_stop_text">Tap to stop</string> <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] --> @@ -250,6 +266,8 @@ <string name="screenrecord_delete_error">Error deleting screen recording</string> <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] --> <string name="screenrecord_permission_error">Failed to get permissions</string> + <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] --> + <string name="screenrecord_start_error">Error starting screen recording</string> <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] --> <string name="usb_preference_title">USB file transfer options</string> @@ -1812,16 +1830,16 @@ <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">Favorite</string> + <string name="notification_conversation_favorite">Mark as important</string> <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> - <string name="notification_conversation_unfavorite">Unfavorite</string> + <string name="notification_conversation_unfavorite">Mark as unimportant</string> <!-- [CHAR LIMIT=100] Mute this conversation --> - <string name="notification_conversation_mute">Mute</string> + <string name="notification_conversation_mute">Silence</string> <!-- [CHAR LIMIT=100] Umute this conversation --> - <string name="notification_conversation_unmute">Unmute</string> + <string name="notification_conversation_unmute">Alerting</string> <!-- [CHAR LIMIT=100] Show notification as bubble --> <string name="notification_conversation_bubble">Show as bubble</string> diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index b2423b9bf252..85724a969fed 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -27,7 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; @@ -47,7 +47,6 @@ public class AdminSecondaryLockScreenController { private Handler mHandler; private IKeyguardClient mClient; private KeyguardSecurityCallback mKeyguardCallback; - private SurfaceControl.Transaction mTransaction; private final ServiceConnection mConnection = new ServiceConnection() { @Override @@ -84,13 +83,13 @@ public class AdminSecondaryLockScreenController { } @Override - public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) { + public void onRemoteContentReady( + @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } - if (remoteSurfaceControl != null) { - mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl()) - .apply(); + if (surfacePackage != null) { + mView.setChildSurfacePackage(surfacePackage); } else { dismiss(KeyguardUpdateMonitor.getCurrentUser()); } @@ -138,11 +137,10 @@ public class AdminSecondaryLockScreenController { public AdminSecondaryLockScreenController(Context context, ViewGroup parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - Handler handler, SurfaceControl.Transaction transaction) { + Handler handler) { mContext = context; mHandler = handler; mParent = parent; - mTransaction = transaction; mUpdateMonitor = updateMonitor; mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 29c67ae1b4a6..ba8a1a945a77 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,7 +39,6 @@ import android.util.Slog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.SurfaceControl; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -149,8 +148,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe mViewConfiguration = ViewConfiguration.get(context); mKeyguardStateController = Dependency.get(KeyguardStateController.class); mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, - mUpdateMonitor, mCallback, new Handler(Looper.myLooper()), - new SurfaceControl.Transaction()); + mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); } public void setSecurityCallback(SecurityCallback callback) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 6a04583b70ba..e5f6d3c90eb7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -21,15 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; -import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; -import static android.os.BatteryManager.BATTERY_STATUS_FULL; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; -import static android.os.BatteryManager.EXTRA_HEALTH; -import static android.os.BatteryManager.EXTRA_LEVEL; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; -import static android.os.BatteryManager.EXTRA_PLUGGED; -import static android.os.BatteryManager.EXTRA_STATUS; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM; @@ -67,7 +59,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.media.AudioManager; -import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.IRemoteCallback; @@ -94,6 +85,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.DejankUtils; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; @@ -1059,29 +1051,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab MSG_TIMEZONE_UPDATE, intent.getStringExtra("time-zone")); mHandler.sendMessage(msg); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { - final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); - final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); - final int level = intent.getIntExtra(EXTRA_LEVEL, 0); - final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); - final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); - int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); - final int maxChargingMicroWatt; - - if (maxChargingMicroVolt <= 0) { - maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; - } - if (maxChargingMicroAmp > 0) { - // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor - // to maintain precision equally on both factors. - maxChargingMicroWatt = (maxChargingMicroAmp / 1000) - * (maxChargingMicroVolt / 1000); - } else { - maxChargingMicroWatt = -1; - } final Message msg = mHandler.obtainMessage( - MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health, - maxChargingMicroWatt)); + MSG_BATTERY_UPDATE, new BatteryStatus(intent)); mHandler.sendMessage(msg); } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) { SimData args = SimData.fromIntent(intent); @@ -1323,82 +1295,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - public static class BatteryStatus { - public static final int CHARGING_UNKNOWN = -1; - public static final int CHARGING_SLOWLY = 0; - public static final int CHARGING_REGULAR = 1; - public static final int CHARGING_FAST = 2; - - public final int status; - public final int level; - public final int plugged; - public final int health; - public final int maxChargingWattage; - - public BatteryStatus(int status, int level, int plugged, int health, - int maxChargingWattage) { - this.status = status; - this.level = level; - this.plugged = plugged; - this.health = health; - this.maxChargingWattage = maxChargingWattage; - } - - /** - * Determine whether the device is plugged in (USB, power, or wireless). - * - * @return true if the device is plugged in. - */ - public boolean isPluggedIn() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB - || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; - } - - /** - * Determine whether the device is plugged in (USB, power). - * - * @return true if the device is plugged in wired (as opposed to wireless) - */ - public boolean isPluggedInWired() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } - - /** - * Whether or not the device is charged. Note that some devices never return 100% for - * battery level, so this allows either battery level or status to determine if the - * battery is charged. - * - * @return true if the device is charged - */ - public boolean isCharged() { - return status == BATTERY_STATUS_FULL || level >= 100; - } - - /** - * Whether battery is low and needs to be charged. - * - * @return true if battery is low - */ - public boolean isBatteryLow() { - return level < LOW_BATTERY_THRESHOLD; - } - - public final int getChargingSpeed(int slowThreshold, int fastThreshold) { - return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : - maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : - maxChargingWattage > fastThreshold ? CHARGING_FAST : - CHARGING_REGULAR; - } - - @Override - public String toString() { - return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged - + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; - } - } - public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { private final Consumer<Integer> mStrongAuthRequiredChangedCallback; @@ -2640,9 +2536,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private boolean refreshSimState(int subId, int slotId) { final TelephonyManager tele = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); int state = (tele != null) ? - tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; + tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 8e87b7ad45b9..49f72a925a0e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -42,7 +43,7 @@ public class KeyguardUpdateMonitorCallback { * * @param status current battery status */ - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } + public void onRefreshBatteryInfo(BatteryStatus status) { } /** * Called once per minute or when the time changes. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index b8d32aec30e3..8a492a83b3df 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -87,7 +87,7 @@ public class AuthContainerView extends LinearLayout @VisibleForTesting @Nullable AuthBiometricView mBiometricView; @VisibleForTesting @Nullable AuthCredentialView mCredentialView; - private final ImageView mBackgroundView; + @VisibleForTesting final ImageView mBackgroundView; @VisibleForTesting final ScrollView mBiometricScrollView; private final View mPanelView; @@ -333,6 +333,12 @@ public class AuthContainerView extends LinearLayout throw new IllegalStateException("Unknown credential type: " + credentialType); } + // The background is used for detecting taps / cancelling authentication. Since the + // credential view is full-screen and should not be canceled from background taps, + // disable it. + mBackgroundView.setOnClickListener(null); + mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mCredentialView.setContainerView(this); mCredentialView.setEffectiveUserId(mEffectiveUserId); mCredentialView.setCredentialType(credentialType); @@ -583,11 +589,13 @@ public class AuthContainerView extends LinearLayout * @return */ public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) { + final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_SECURE; final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + windowFlags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("BiometricPrompt"); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 7c07c9d81000..2f1e4b41bd2d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -970,7 +970,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * The cancellation of summaries with children associated with bubbles are also handled in this * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. * - * @return true if we want to intercept the dismissal of the entry, else false + * @return true if we want to intercept the dismissal of the entry, else false. */ public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) { if (entry == null) { @@ -1010,7 +1010,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble notification sticks around in the data as long as the bubble is // not dismissed and the app hasn't cancelled the notification. Bubble bubble = mBubbleData.getBubbleWithKey(key); - boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif; + boolean bubbleExtended = entry != null && entry.isBubble() + && (userRemovedNotif || isUserCreatedBubble(bubble.getKey())); if (bubbleExtended) { bubble.setSuppressNotification(true); bubble.setShowDot(false /* show */, true /* animate */); @@ -1019,8 +1020,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi + ".shouldInterceptDismissal"); } return true; - } else if (!userRemovedNotif && entry != null - && !isUserCreatedBubble(bubble.getKey())) { + } else if (!userRemovedNotif && entry != null) { // This wasn't a user removal so we should remove the bubble as well mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); return false; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 6062a3d45be0..20d19ece575c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -533,10 +533,6 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(mOverflowBtn, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mOverflowBtn.setOnClickListener(v -> { - setSelectedBubble(null); - }); - TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.colorBackgroundFloating}); int bgColor = ta.getColor(0, Color.WHITE /* default */); @@ -856,6 +852,10 @@ public class BubbleStackView extends FrameLayout { updateBubbleZOrdersAndDotPosition(false /* animate */); } + void showOverflow() { + setSelectedBubble(null); + } + /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the @@ -950,6 +950,10 @@ public class BubbleStackView extends FrameLayout { } if (mIsExpanded) { if (isIntersecting(mBubbleContainer, x, y)) { + if (BubbleExperimentConfig.allowBubbleOverflow(mContext) + && isIntersecting(mOverflowBtn, x, y)) { + return mOverflowBtn; + } // Could be tapping or dragging a bubble while expanded for (int i = 0; i < getBubbleCount(); i++) { BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index fdeaf1f016c3..5a9d44b6da2c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.systemui.Dependency; +import com.android.systemui.R; /** * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, @@ -109,6 +110,10 @@ class BubbleTouchHandler implements View.OnTouchListener { if (!(mTouchedView instanceof BadgedImageView) && !(mTouchedView instanceof BubbleStackView) && !(mTouchedView instanceof BubbleFlyoutView)) { + + if (mTouchedView.getId() == R.id.bubble_overflow_button) { + mStack.showOverflow(); + } // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge // of expanded view). mStack.hideBubbleMenu(); 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 a6f1d84877c5..7de1557ebc65 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -19,9 +19,12 @@ package com.android.systemui.controls.controller import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.database.ContentObserver +import android.net.Uri import android.os.Environment import android.os.UserHandle import android.provider.Settings @@ -30,6 +33,7 @@ import android.service.controls.actions.ControlAction import android.util.ArrayMap import android.util.Log import com.android.internal.annotations.GuardedBy +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.DumpController import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher @@ -53,15 +57,16 @@ class ControlsControllerImpl @Inject constructor ( private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, private val listingController: ControlsListingController, - broadcastDispatcher: BroadcastDispatcher, + private val broadcastDispatcher: BroadcastDispatcher, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpController: DumpController ) : Dumpable, ControlsController { companion object { private const val TAG = "ControlsControllerImpl" - const val CONTROLS_AVAILABLE = "systemui.controls_available" - const val USER_CHANGE_RETRY_DELAY = 500L // ms + internal const val CONTROLS_AVAILABLE = "systemui.controls_available" + internal val URI = Settings.Secure.getUriFor(CONTROLS_AVAILABLE) + private const val USER_CHANGE_RETRY_DELAY = 500L // ms } // Map of map: ComponentName -> (String -> ControlInfo). @@ -69,9 +74,11 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() - private var userChanging = true - override var available = Settings.Secure.getInt( - context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + private var userChanging: Boolean = true + + private val contentResolver: ContentResolver + get() = context.contentResolver + override var available = Settings.Secure.getInt(contentResolver, CONTROLS_AVAILABLE, 0) != 0 private set private var currentUser = context.user @@ -95,8 +102,8 @@ class ControlsControllerImpl @Inject constructor ( val fileName = Environment.buildPath( userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME) persistenceWrapper.changeFile(fileName) - available = Settings.Secure.getIntForUser( - context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, newUser.identifier) != 0 synchronized(currentFavorites) { currentFavorites.clear() } @@ -123,6 +130,25 @@ class ControlsControllerImpl @Inject constructor ( } } + @VisibleForTesting + internal val settingObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean, uri: Uri, userId: Int) { + // Do not listen to changes in the middle of user change, those will be read by the + // user-switch receiver. + if (userChanging || userId != currentUserId) { + return + } + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, currentUserId) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + } + } + init { dumpController.registerDumpable(this) if (available) { @@ -135,6 +161,7 @@ class ControlsControllerImpl @Inject constructor ( executor, UserHandle.ALL ) + contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL) } private fun confirmAvailability(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index fad2d94d6cf3..88b19b58a453 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -34,14 +34,17 @@ import android.widget.ImageView import android.widget.TextView import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.R const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 +private const val UPDATE_DELAY_IN_MILLIS = 2000L class ControlViewHolder( val layout: ViewGroup, - val controlsController: ControlsController + val controlsController: ControlsController, + val uiExecutor: DelayableExecutor ) { val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) @@ -52,6 +55,7 @@ class ControlViewHolder( val clipLayer: ClipDrawable val gd: GradientDrawable lateinit var cws: ControlWithState + var cancelUpdate: Runnable? = null init { val ld = layout.getBackground() as LayerDrawable @@ -63,6 +67,8 @@ class ControlViewHolder( fun bindData(cws: ControlWithState) { this.cws = cws + cancelUpdate?.run() + val (status, template) = cws.control?.let { title.setText(it.getTitle()) subtitle.setText(it.getSubtitle()) @@ -86,6 +92,27 @@ class ControlViewHolder( findBehavior(status, template).apply(this, cws) } + fun actionResponse(@ControlAction.ResponseResult response: Int) { + val text = when (response) { + ControlAction.RESPONSE_OK -> "Success" + ControlAction.RESPONSE_FAIL -> "Error" + else -> "" + } + + if (!text.isEmpty()) { + val previousText = status.getText() + val previousTextExtra = statusExtra.getText() + + cancelUpdate = uiExecutor.executeDelayed({ + status.setText(previousText) + statusExtra.setText(previousTextExtra) + }, UPDATE_DELAY_IN_MILLIS) + + status.setText(text) + statusExtra.setText("") + } + } + fun action(action: ControlAction) { controlsController.action(cws.ci, action) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index b07a75d5e757..d70c86fc3266 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -22,6 +22,8 @@ import android.service.controls.actions.ControlAction import android.view.ViewGroup interface ControlsUiController { + val available: Boolean + fun show(parent: ViewGroup) fun hide() fun onRefreshState(componentName: ComponentName, controls: List<Control>) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index a777faf57fce..ed521e3be535 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -38,10 +38,10 @@ import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.R +import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy -import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -104,18 +104,23 @@ class TokenProviderConnection(val cc: ControlsController, val context: Context) } } +private data class ControlKey(val componentName: ComponentName, val controlId: String) + @Singleton class ControlsUiControllerImpl @Inject constructor ( val controlsController: Lazy<ControlsController>, val context: Context, - @Main val uiExecutor: Executor + @Main val uiExecutor: DelayableExecutor ) : ControlsUiController { private lateinit var controlInfos: List<ControlInfo> - private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>() - private val controlViewsById = mutableMapOf<String, ControlViewHolder>() + private val controlsById = mutableMapOf<ControlKey, ControlWithState>() + private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() private lateinit var parent: ViewGroup + override val available: Boolean + get() = controlsController.get().available + override fun show(parent: ViewGroup) { Log.d(TAG, "show()") @@ -125,7 +130,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlInfos.map { ControlWithState(it, null) - }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) } + }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) } if (controlInfos.isEmpty()) { showInitialSetupView() @@ -178,9 +183,10 @@ class ControlsUiControllerImpl @Inject constructor ( val item = inflater.inflate( R.layout.controls_base_item, lastRow, false) as ViewGroup lastRow.addView(item) - val cvh = ControlViewHolder(item, controlsController.get()) - cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!) - controlViewsById.put(it.controlId, cvh) + val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor) + val key = ControlKey(it.component, it.controlId) + cvh.bindData(controlsById.getValue(key)) + controlViewsById.put(key, cvh) } if ((controlInfos.size % 2) == 1) { @@ -205,21 +211,24 @@ class ControlsUiControllerImpl @Inject constructor ( override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { Log.d(TAG, "onRefreshState()") controls.forEach { c -> - controlsById.get(Pair(componentName, c.getControlId()))?.let { + controlsById.get(ControlKey(componentName, c.getControlId()))?.let { Log.d(TAG, "onRefreshState() for id: " + c.getControlId()) val cws = ControlWithState(it.ci, c) - controlsById.put(Pair(componentName, c.getControlId()), cws) + val key = ControlKey(componentName, c.getControlId()) + controlsById.put(key, cws) uiExecutor.execute { - controlViewsById.get(c.getControlId())?.bindData(cws) + controlViewsById.get(key)?.bindData(cws) } } } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - Log.d(TAG, "onActionResponse()") - TODO("not implemented") + val key = ControlKey(componentName, controlId) + uiExecutor.execute { + controlViewsById.get(key)?.actionResponse(response) + } } private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index 093c99f57a9a..da52c6f8ee21 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -108,10 +108,18 @@ private val deviceIconMap = mapOf<Int, IconState>( DeviceTypes.TYPE_OUTLET to IconState( R.drawable.ic_power_off_gm2_24px, R.drawable.ic_power_gm2_24px + ), + DeviceTypes.TYPE_VACUUM to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px + ), + DeviceTypes.TYPE_MOP to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px ) ).withDefault { IconState( - R.drawable.ic_light_off_gm2_24px, - R.drawable.ic_lightbulb_outline_gm2_24px + R.drawable.ic_device_unknown_gm2_24px, + R.drawable.ic_device_unknown_gm2_24px ) } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 45c07a3e4693..b3fc027d1ac7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1725,7 +1725,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); + ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE + : Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | @@ -1899,9 +1900,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private boolean shouldShowControls() { - return isCurrentUserOwner() - && !mKeyguardManager.isDeviceLocked() - && Settings.Secure.getInt(mContext.getContentResolver(), - "systemui.controls_available", 0) == 1; + return !mKeyguardManager.isDeviceLocked() + && mControlsUiController.getAvailable(); } } diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java deleted file mode 100644 index 7bc1abfbb0d8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/Event.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 com.android.systemui.log; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every event has a time stamp, log level and message. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public class Event { - public static final int UNINITIALIZED = -1; - - @IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE}) - @Retention(RetentionPolicy.SOURCE) - public @interface Level {} - public static final int VERBOSE = 2; - public static final int DEBUG = 3; - public static final int INFO = 4; - public static final int WARN = 5; - public static final int ERROR = 6; - public static final @Level int DEFAULT_LOG_LEVEL = DEBUG; - - private long mTimestamp; - private @Level int mLogLevel = DEFAULT_LOG_LEVEL; - private String mMessage = ""; - - /** - * initialize an event with a message - */ - public Event init(String message) { - init(DEFAULT_LOG_LEVEL, message); - return this; - } - - /** - * initialize an event with a logLevel and message - */ - public Event init(@Level int logLevel, String message) { - mTimestamp = System.currentTimeMillis(); - mLogLevel = logLevel; - mMessage = message; - return this; - } - - public String getMessage() { - return mMessage; - } - - public long getTimestamp() { - return mTimestamp; - } - - public @Level int getLogLevel() { - return mLogLevel; - } - - /** - * Recycle this event - */ - void recycle() { - mTimestamp = -1; - mLogLevel = DEFAULT_LOG_LEVEL; - mMessage = ""; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java deleted file mode 100644 index 470f2b0d1b98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 com.android.systemui.log; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every rich event has a time stamp, event type, and log level, with the option to provide the - * reason this event was triggered. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public abstract class RichEvent extends Event { - private int mType; - - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public RichEvent init(@Event.Level int logLevel, int type, String reason) { - final int numEvents = getEventLabels().length; - if (type < 0 || type >= numEvents) { - throw new IllegalArgumentException("Unsupported event type. Events only supported" - + " from 0 to " + (numEvents - 1) + ", but given type=" + type); - } - mType = type; - super.init(logLevel, getEventLabels()[mType] + " " + reason); - return this; - } - - /** - * Returns an array of the event labels. The index represents the event type and the - * corresponding String stored at that index is the user-readable representation of that event. - * @return array of user readable events, where the index represents its event type constant - */ - public abstract String[] getEventLabels(); - - @Override - public void recycle() { - super.recycle(); - mType = -1; - } - - public int getType() { - return mType; - } - - /** - * Builder to build a RichEvent. - * @param <B> Log specific builder that is extending this builder - * @param <E> Type of event we'll be building - */ - public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> { - public static final int UNINITIALIZED = -1; - - public final SysuiLog mLog; - private B mBuilder = getBuilder(); - protected int mType; - protected String mReason; - protected @Level int mLogLevel; - - public Builder(SysuiLog sysuiLog) { - mLog = sysuiLog; - reset(); - } - - /** - * Reset this builder's parameters so it can be reused to build another RichEvent. - */ - public void reset() { - mType = UNINITIALIZED; - mReason = null; - mLogLevel = VERBOSE; - } - - /** - * Get the log-specific builder. - */ - public abstract B getBuilder(); - - /** - * Build the log-specific event given an event to populate. - */ - public abstract E build(E e); - - /** - * Optional - set the log level. Defaults to DEBUG. - */ - public B setLogLevel(@Level int logLevel) { - mLogLevel = logLevel; - return mBuilder; - } - - /** - * Required - set the event type. These events must correspond with the events from - * getEventLabels(). - */ - public B setType(int type) { - mType = type; - return mBuilder; - } - - /** - * Optional - set the reason why this event was triggered. - */ - public B setReason(String reason) { - mReason = reason; - return mBuilder; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java deleted file mode 100644 index 9ee3e6765e4a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 com.android.systemui.log; - -import android.os.Build; -import android.os.SystemProperties; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.DumpController; -import com.android.systemui.Dumpable; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayDeque; -import java.util.Locale; - -/** - * Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be - * printed by the DumpController. This is an alternative to printing directly - * to avoid logs being deleted by chatty. The number of logs retained is varied based on - * whether the build is {@link Build.IS_DEBUGGABLE}. - * - * To manually view the logs via adb: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController <SysuiLogId> - * - * Logs can be disabled by setting the following SystemProperty and then restarting the device: - * adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot - * - * @param <E> Type of event we'll be logging - */ -public class SysuiLog<E extends Event> implements Dumpable { - public static final SimpleDateFormat DATE_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US); - - protected final Object mDataLock = new Object(); - private final String mId; - private final int mMaxLogs; - protected boolean mEnabled; - protected boolean mLogToLogcatEnabled; - - @VisibleForTesting protected ArrayDeque<E> mTimeline; - - /** - * Creates a SysuiLog - * @param dumpController where to register this logger's dumpsys - * @param id user-readable tag for this logger - * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true - * @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false - */ - public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) { - this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs, - SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED), - SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id, - DEFAULT_LOGCAT_ENABLED)); - } - - @VisibleForTesting - protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled, - boolean logcatEnabled) { - mId = id; - mMaxLogs = maxLogs; - mEnabled = enabled; - mLogToLogcatEnabled = logcatEnabled; - mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null; - dumpController.registerDumpable(mId, this); - } - - /** - * Logs an event to the timeline which can be printed by the dumpsys. - * May also log to logcat if enabled. - * @return the last event that was discarded from the Timeline (can be recycled) - */ - public E log(E event) { - if (!mEnabled) { - return null; - } - - E recycledEvent = null; - synchronized (mDataLock) { - if (mTimeline.size() >= mMaxLogs) { - recycledEvent = mTimeline.removeFirst(); - } - - mTimeline.add(event); - } - - if (mLogToLogcatEnabled) { - final String strEvent = eventToString(event); - switch (event.getLogLevel()) { - case Event.VERBOSE: - Log.v(mId, strEvent); - break; - case Event.DEBUG: - Log.d(mId, strEvent); - break; - case Event.ERROR: - Log.e(mId, strEvent); - break; - case Event.INFO: - Log.i(mId, strEvent); - break; - case Event.WARN: - Log.w(mId, strEvent); - break; - } - } - - if (recycledEvent != null) { - recycledEvent.recycle(); - } - - return recycledEvent; - } - - /** - * @return user-readable string of the given event with timestamp - */ - private String eventToTimestampedString(Event event) { - StringBuilder sb = new StringBuilder(); - sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp())); - sb.append(" "); - sb.append(event.getMessage()); - return sb.toString(); - } - - /** - * @return user-readable string of the given event without a timestamp - */ - public String eventToString(Event event) { - return event.getMessage(); - } - - @GuardedBy("mDataLock") - private void dumpTimelineLocked(PrintWriter pw) { - pw.println("\tTimeline:"); - - for (Event event : mTimeline) { - pw.println("\t" + eventToTimestampedString(event)); - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(mId + ":"); - - if (mEnabled) { - synchronized (mDataLock) { - dumpTimelineLocked(pw); - } - } else { - pw.print(" - Logging disabled."); - } - } - - private static boolean sDebuggable = Build.IS_DEBUGGABLE; - private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled."; - private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat."; - private static final boolean DEFAULT_ENABLED = sDebuggable; - private static final boolean DEFAULT_LOGCAT_ENABLED = false; - private static final int DEFAULT_MAX_DEBUG_LOGS = 100; - private static final int DEFAULT_MAX_LOGS = 50; -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index eba2e7874574..3ae627d27def 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -19,10 +19,6 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import android.animation.AnimationHandler; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeAnimator; import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; @@ -36,6 +32,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.view.Choreographer; import androidx.dynamicanimation.animation.SpringForce; @@ -88,9 +85,11 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** PIP's current bounds on the screen. */ private final Rect mBounds = new Rect(); + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider = + new SfVsyncFrameCallbackProvider(); + /** - * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever - * the {@link #mVsyncTimeAnimator} ticks. + * Bounds that are animated using the physics animator. */ private final Rect mAnimatedBounds = new Rect(); @@ -100,12 +99,16 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mAnimatedBounds); + /** Callback that re-sizes PIP to the animated bounds. */ + private final Choreographer.FrameCallback mResizePipVsyncCallback = + l -> resizePipUnchecked(mAnimatedBounds); + /** - * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each - * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using - * physics animations. + * Update listener that posts a vsync frame callback to resize PIP to {@link #mAnimatedBounds}. */ - private TimeAnimator mVsyncTimeAnimator; + private final PhysicsAnimator.UpdateListener<Rect> mResizePipVsyncUpdateListener = + (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -126,39 +129,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFlingAnimationUtils = flingAnimationUtils; - final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler(); - vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider()); - onConfigurationChanged(); - - // Construct a time animator that uses the vsync frame provider. Physics animations can't - // use custom frame providers, since they rely on constant time between frames to run the - // physics simulations. To work around this, we physically-animate a second set of bounds, - // and apply those animating bounds to the PIP in-sync via this TimeAnimator. - mVsyncTimeAnimator = new TimeAnimator() { - @Override - public AnimationHandler getAnimationHandler() { - return vsyncFrameCallbackProvider; - } - }; - - // When the time animator ticks, move PIP to the animated bounds. - mVsyncTimeAnimator.setTimeListener( - (animation, totalTime, deltaTime) -> - resizePipUnchecked(mAnimatedBounds)); - - // Add a listener for cancel/end events that moves PIP to the final animated bounds. - mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - - @Override - public void onAnimationEnd(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - }); } /** @@ -429,7 +400,6 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call */ private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); - mVsyncTimeAnimator.cancel(); } /** @@ -457,10 +427,8 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call cancelAnimations(); mAnimatedBoundsPhysicsAnimator - .withEndActions( - mVsyncTimeAnimator::cancel) + .addUpdateListener(mResizePipVsyncUpdateListener) .start(); - mVsyncTimeAnimator.start(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0134aa3a15df..5de6d1c42b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -169,7 +169,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { v.setText(mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID)); v.setVisibility(View.VISIBLE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 9e3e94ce4186..6c697184f082 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -36,6 +36,7 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.os.Handler; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -370,6 +371,13 @@ public class QSMediaPlayer { if (mSeamless == null) { return; } + Handler handler = mSeamless.getHandler(); + handler.post(() -> { + updateChipInternal(device); + }); + } + + private void updateChipInternal(MediaDevice device) { ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor); // Update the outline color diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index b091ad8c0db8..626f298055a0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord; -import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -26,16 +25,21 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.MediaRecorder; +import android.media.projection.IMediaProjection; +import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.MediaStore; import android.provider.Settings; import android.util.DisplayMetrics; @@ -83,7 +87,6 @@ public class RecordingService extends Service { private static final int AUDIO_SAMPLE_RATE = 44100; private final RecordingController mController; - private MediaProjectionManager mMediaProjectionManager; private MediaProjection mMediaProjection; private Surface mInputSurface; private VirtualDisplay mVirtualDisplay; @@ -134,13 +137,30 @@ public class RecordingService extends Service { switch (action) { case ACTION_START: - int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED); mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false); mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); - Intent data = intent.getParcelableExtra(EXTRA_DATA); - if (data != null) { - mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data); + try { + IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); + IMediaProjectionManager mediaService = + IMediaProjectionManager.Stub.asInterface(b); + IMediaProjection proj = mediaService.createProjection(getUserId(), + getPackageName(), + MediaProjectionManager.TYPE_SCREEN_CAPTURE, false); + IBinder projection = proj.asBinder(); + if (projection == null) { + Log.e(TAG, "Projection was null"); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; + } + mMediaProjection = new MediaProjection(getApplicationContext(), + IMediaProjection.Stub.asInterface(projection)); startRecording(); + } catch (RemoteException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; } break; @@ -195,9 +215,6 @@ public class RecordingService extends Service { @Override public void onCreate() { super.onCreate(); - - mMediaProjectionManager = - (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); } /** @@ -269,6 +286,7 @@ public class RecordingService extends Service { } private void createRecordingNotification() { + Resources res = getResources(); NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), @@ -281,11 +299,15 @@ public class RecordingService extends Service { Bundle extras = new Bundle(); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getResources().getString(R.string.screenrecord_name)); + res.getString(R.string.screenrecord_name)); + + String notificationTitle = mUseAudio + ? res.getString(R.string.screenrecord_ongoing_screen_and_audio) + : res.getString(R.string.screenrecord_ongoing_screen_only); mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) + .setContentTitle(notificationTitle) .setContentText(getResources().getString(R.string.screenrecord_stop_text)) .setUsesChronometer(true) .setColorized(true) @@ -332,8 +354,7 @@ public class RecordingService extends Service { Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) - .setContentText(getResources().getString(R.string.screenrecord_save_message)) + .setContentTitle(getResources().getString(R.string.screenrecord_save_message)) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 8324986123bd..566f12b42795 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -16,15 +16,14 @@ package com.android.systemui.screenrecord; -import android.Manifest; import android.app.Activity; import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.media.projection.MediaProjectionManager; import android.os.Bundle; -import android.widget.Toast; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.Switch; import com.android.systemui.R; @@ -34,15 +33,11 @@ import javax.inject.Inject; * Activity to select screen recording options */ public class ScreenRecordDialog extends Activity { - private static final int REQUEST_CODE_VIDEO_ONLY = 200; - private static final int REQUEST_CODE_VIDEO_TAPS = 201; - private static final int REQUEST_CODE_PERMISSIONS = 299; - private static final int REQUEST_CODE_VIDEO_AUDIO = 300; - private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301; - private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399; private static final long DELAY_MS = 3000; private final RecordingController mController; + private Switch mAudioSwitch; + private Switch mTapsSwitch; @Inject public ScreenRecordDialog(RecordingController controller) { @@ -52,81 +47,42 @@ public class ScreenRecordDialog extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestScreenCapture(); - } - private void requestScreenCapture() { - MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService( - Context.MEDIA_PROJECTION_SERVICE); - Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); + Window window = getWindow(); + // Inflate the decor view, so the attributes below are not overwritten by the theme. + window.getDecorView(); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.setGravity(Gravity.TOP); + + setContentView(R.layout.screen_record_dialog); + + Button cancelBtn = findViewById(R.id.button_cancel); + cancelBtn.setOnClickListener(v -> { + finish(); + }); - // TODO get saved settings - boolean useAudio = false; - boolean showTaps = false; - if (useAudio) { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO); - } else { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY); - } + Button startBtn = findViewById(R.id.button_start); + startBtn.setOnClickListener(v -> { + requestScreenCapture(); + finish(); + }); + + mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); + mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - switch (requestCode) { - case REQUEST_CODE_VIDEO_TAPS: - case REQUEST_CODE_VIDEO_AUDIO_TAPS: - case REQUEST_CODE_VIDEO_ONLY: - case REQUEST_CODE_VIDEO_AUDIO: - if (resultCode == RESULT_OK) { - PendingIntent startIntent = PendingIntent.getForegroundService( - this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent( - ScreenRecordDialog.this, resultCode, data, useAudio, - showTaps), - PendingIntent.FLAG_UPDATE_CURRENT - ); - PendingIntent stopIntent = PendingIntent.getService( - this, RecordingService.REQUEST_CODE, - RecordingService.getStopIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - mController.startCountdown(DELAY_MS, startIntent, stopIntent); - } else { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - } - finish(); - break; - case REQUEST_CODE_PERMISSIONS: - int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (permission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - case REQUEST_CODE_PERMISSIONS_AUDIO: - int videoPermission = checkSelfPermission( - Manifest.permission.WRITE_EXTERNAL_STORAGE); - int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); - if (videoPermission != PackageManager.PERMISSION_GRANTED - || audioPermission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - } + private void requestScreenCapture() { + boolean useAudio = mAudioSwitch.isChecked(); + boolean showTaps = mTapsSwitch.isChecked(); + PendingIntent startIntent = PendingIntent.getForegroundService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStartIntent( + ScreenRecordDialog.this, RESULT_OK, null, useAudio, showTaps), + PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent stopIntent = PendingIntent.getService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + mController.startCountdown(DELAY_MS, startIntent, stopIntent); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java index 22fd37ceaebd..eb580c450730 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java @@ -22,11 +22,4 @@ package com.android.systemui.statusbar; */ public interface InflationTask { void abort(); - - /** - * Supersedes an existing task. i.e another task was superceeded by this. - * - * @param task the task that was previously running - */ - default void supersedeTask(InflationTask task) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7ad07c266cc3..7d3d4061014b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Resources; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -46,6 +45,7 @@ import com.android.internal.widget.ViewClippingUtil; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -97,8 +97,6 @@ public class KeyguardIndicationController implements StateListener, private final LockPatternUtils mLockPatternUtils; private final DockManager mDockManager; - private final int mSlowThreshold; - private final int mFastThreshold; private final LockIcon mLockIcon; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); @@ -178,10 +176,6 @@ public class KeyguardIndicationController implements StateListener, mWakeLock = new SettableWakeLock(wakeLock, TAG); mLockPatternUtils = lockPatternUtils; - Resources res = context.getResources(); - mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold); - mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold); - mUserManager = context.getSystemService(UserManager.class); mBatteryInfo = iBatteryStats; @@ -484,12 +478,12 @@ public class KeyguardIndicationController implements StateListener, int chargingId; if (mPowerPluggedInWired) { switch (mChargingSpeed) { - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: + case BatteryStatus.CHARGING_FAST: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_fast : R.string.keyguard_plugged_in_charging_fast; break; - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: + case BatteryStatus.CHARGING_SLOWLY: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_slowly : R.string.keyguard_plugged_in_charging_slowly; @@ -620,7 +614,7 @@ public class KeyguardIndicationController implements StateListener, public static final int HIDE_DELAY_MS = 5000; @Override - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { + public void onRefreshBatteryInfo(BatteryStatus status) { boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING || status.status == BatteryManager.BATTERY_STATUS_FULL; boolean wasPluggedIn = mPowerPluggedIn; @@ -628,7 +622,7 @@ public class KeyguardIndicationController implements StateListener, mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; - mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); + mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; try { mChargingTimeRemaining = mPowerPluggedIn diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index 81833a4022a9..d0e238a66330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -28,7 +28,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.HeadsUpManager; import javax.inject.Inject; @@ -64,8 +63,8 @@ public class NotificationAlertingManager { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryInflated(NotificationEntry entry, int inflatedFlags) { - showAlertingView(entry, inflatedFlags); + public void onEntryInflated(NotificationEntry entry) { + showAlertingView(entry); } @Override @@ -90,12 +89,11 @@ public class NotificationAlertingManager { /** * Adds the entry to the respective alerting manager if the content view was inflated and * the entry should still alert. - * - * @param entry entry to add - * @param inflatedFlags flags representing content views that were inflated */ - private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) { - if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + private void showAlertingView(NotificationEntry entry) { + // TODO: Instead of this back and forth, we should listen to changes in heads up and + // cancel on-going heads up view inflation using the bind pipeline. + if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { // Possible for shouldHeadsUp to change between the inflation starting and ending. // If it does and we no longer need to heads up, we should free the view. if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index f6b55838989c..25253a15b125 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -24,7 +24,6 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; /** * Listener interface for changes sent by NotificationEntryManager. @@ -62,7 +61,7 @@ public interface NotificationEntryListener { /** * Called when a notification's views are inflated for the first time. */ - default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) { + default void onEntryInflated(NotificationEntry entry) { } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 6bb377e89278..916da6eca0c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_ERROR; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import android.annotation.NonNull; @@ -44,10 +45,9 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -96,6 +96,7 @@ import dagger.Lazy; */ @Singleton public class NotificationEntryManager implements + CommonNotifCollection, Dumpable, InflationCallback, VisualStabilityManager.Callback { @@ -126,10 +127,13 @@ public class NotificationEntryManager implements private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); + private final NotificationEntryManagerLogger mLogger; + // Lazily retrieved dependencies private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy; private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy; private final LeakDetector mLeakDetector; + private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManager mGroupManager; @@ -139,7 +143,6 @@ public class NotificationEntryManager implements private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; - private NotifLog mNotifLog; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders @@ -180,7 +183,7 @@ public class NotificationEntryManager implements @Inject public NotificationEntryManager( - NotifLog notifLog, + NotificationEntryManagerLogger logger, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, @@ -189,7 +192,7 @@ public class NotificationEntryManager implements Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, LeakDetector leakDetector, ForegroundServiceDismissalFeatureController fgsFeatureController) { - mNotifLog = notifLog; + mLogger = logger; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; @@ -287,13 +290,12 @@ public class NotificationEntryManager implements NotificationEntry entry = mPendingNotifications.get(key); entry.abortTask(); mPendingNotifications.remove(key); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted" - + " reason=" + reason); + mLogger.logInflationAborted(key, "pending", reason); } NotificationEntry addedEntry = getActiveNotificationUnfiltered(key); if (addedEntry != null) { addedEntry.abortTask(); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason); + mLogger.logInflationAborted(key, "active", reason); } } @@ -318,17 +320,16 @@ public class NotificationEntryManager implements } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mPendingNotifications.remove(entry.getKey()); // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. if (!entry.isRowRemoved()) { boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null; + mLogger.logNotifInflated(entry.getKey(), isNew); if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); - listener.onEntryInflated(entry, inflatedFlags); + listener.onEntryInflated(entry); } addActiveNotification(entry); updateNotifications("onAsyncInflationFinished"); @@ -337,7 +338,6 @@ public class NotificationEntryManager implements } } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryReinflated(entry); } } @@ -419,7 +419,7 @@ public class NotificationEntryManager implements for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) { if (interceptor.onNotificationRemoveRequested(key, entry, reason)) { // Remove intercepted; log and skip - mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED); + mLogger.logRemovalIntercepted(key); return; } } @@ -434,10 +434,7 @@ public class NotificationEntryManager implements if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) { extendLifetime(pendingEntry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - pendingEntry.getSbn(), - "pendingEntry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending"); } } } @@ -457,10 +454,7 @@ public class NotificationEntryManager implements mLatestRankingMap = ranking; extendLifetime(entry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - entry.getSbn(), - "entry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active"); break; } } @@ -483,11 +477,17 @@ public class NotificationEntryManager implements mLeakDetector.trackGarbage(entry); removedByUser |= entryDismissed; - mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(), - "removedByUser=" + removedByUser); + mLogger.logNotifRemoved(entry.getKey(), removedByUser); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryRemoved(entry, visibility, removedByUser); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + // NEM doesn't have a good knowledge of reasons so defaulting to unknown. + listener.onEntryRemoved(entry, REASON_UNKNOWN); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } } } } @@ -553,6 +553,10 @@ public class NotificationEntryManager implements mLeakDetector.trackInstance(entry); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -562,10 +566,13 @@ public class NotificationEntryManager implements abortExistingInflation(key, "addNotification"); mPendingNotifications.put(key, entry); - mNotifLog.log(NotifEvent.NOTIF_ADDED, entry); + mLogger.logNotifAdded(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPendingEntryAdded(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryAdded(entry); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -596,10 +603,13 @@ public class NotificationEntryManager implements entry.setSbn(notification); mGroupManager.onEntryUpdated(entry, oldSbn); - mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry); + mLogger.logNotifUpdated(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryUpdated(entry); + } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -674,6 +684,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationRankingUpdated(rankingMap); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingUpdate(rankingMap); + } } private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { @@ -785,7 +798,7 @@ public class NotificationEntryManager implements //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking /** * @param rankingMap the {@link RankingMap} to apply to the current notification list - * @param reason the reason for calling this method, for {@link NotifLog} + * @param reason the reason for calling this method, which will be logged */ public void updateRanking(RankingMap rankingMap, String reason) { updateRankingAndSort(rankingMap, reason); @@ -862,6 +875,11 @@ public class NotificationEntryManager implements return mReadOnlyNotifications.size() != 0; } + @Override + public void addCollectionListener(NotifCollectionListener listener) { + mNotifCollectionListeners.add(listener); + } + /* * End annexation * ----- diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt new file mode 100644 index 000000000000..4382ab50390a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt @@ -0,0 +1,100 @@ +/* + * 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 + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +/** Logger for [NotificationEntryManager]. */ +class NotificationEntryManagerLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifAdded(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF ADDED $str1" + }) + } + + fun logNotifUpdated(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF UPDATED $str1" + }) + } + + fun logInflationAborted(key: String, status: String, reason: String) { + buffer.log(TAG, DEBUG, { + str1 = key + str2 = status + str3 = reason + }, { + "NOTIF INFLATION ABORTED $str1 notifStatus=$str2 reason=$str3" + }) + } + + fun logNotifInflated(key: String, isNew: Boolean) { + buffer.log(TAG, DEBUG, { + str1 = key + bool1 = isNew + }, { + "NOTIF INFLATED $str1 isNew=$bool1}" + }) + } + + fun logRemovalIntercepted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF REMOVE INTERCEPTED for $str1" + }) + } + + fun logLifetimeExtended(key: String, extenderName: String, status: String) { + buffer.log(TAG, INFO, { + str1 = key + str2 = extenderName + str3 = status + }, { + "NOTIF LIFETIME EXTENDED $str1 extender=$str2 status=$str3" + }) + } + + fun logNotifRemoved(key: String, removedByUser: Boolean) { + buffer.log(TAG, INFO, { + str1 = key + bool1 = removedByUser + }, { + "NOTIF REMOVED $str1 removedByUser=$bool1" + }) + } + + fun logFilterAndSort(reason: String) { + buffer.log(TAG, INFO, { + str1 = reason + }, { + "FILTER AND SORT reason=$str1" + }) + } +} + +private const val TAG = "NotificationEntryMgr"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 7a6d4f1c7d7b..9272e51b6aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -149,9 +149,7 @@ public class NotifInflaterImpl implements NotifInflater { } @Override - public void onAsyncInflationFinished( - NotificationEntry entry, - int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (mExternalInflationCallback != null) { mExternalInflationCallback.onInflationFinished(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 9142388374e3..5767ad93014e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; @@ -66,7 +67,7 @@ import javax.inject.Singleton; * 9. The list is handed off to the view layer to be rendered */ @Singleton -public class NotifPipeline { +public class NotifPipeline implements CommonNotifCollection { private final NotifCollection mNotifCollection; private final ShadeListBuilder mShadeListBuilder; @@ -89,10 +90,7 @@ public class NotifPipeline { return mNotifCollection.getActiveNotifs(); } - /** - * Registers a listener to be informed when there is a notification entry event such as an add, - * update, or remove. - */ + @Override public void addCollectionListener(NotifCollectionListener listener) { mNotifCollection.addCollectionListener(listener); } 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 df65dacf12dd..5dbf47e29407 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 @@ -275,7 +275,12 @@ public final class NotificationEntry extends ListEntry { return mHasInflationError; } - void setHasInflationError(boolean hasError) { + /** + * Set whether the notification has an error while inflating. + * + * TODO: Move this into an inflation error manager class. + */ + public void setHasInflationError(boolean hasError) { mHasInflationError = hasError; } @@ -595,12 +600,8 @@ public final class NotificationEntry extends ListEntry { public void setInflationTask(InflationTask abortableTask) { // abort any existing inflation - InflationTask existing = mRunningTask; abortTask(); mRunningTask = abortableTask; - if (existing != null && mRunningTask != null) { - mRunningTask.supersedeTask(existing); - } } public void onInflationTaskFinished() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 1eeeab3e93cb..2981252f148c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -22,11 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifEvent -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE @@ -53,7 +52,7 @@ open class NotificationRankingManager @Inject constructor( private val groupManager: NotificationGroupManager, private val headsUpManager: HeadsUpManager, private val notifFilter: NotificationFilter, - private val notifLog: NotifLog, + private val logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, private val peopleNotificationIdentifier: PeopleNotificationIdentifier, private val highPriorityProvider: HighPriorityProvider @@ -134,7 +133,7 @@ open class NotificationRankingManager @Inject constructor( entries: Sequence<NotificationEntry>, reason: String ): Sequence<NotificationEntry> { - notifLog.log(NotifEvent.FILTER_AND_SORT, reason) + logger.logFilterAndSort(reason) return entries.filter { !notifFilter.shouldFilterOut(it) } .sortedWith(rankingComparator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 41314b86695a..1e5946a85cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -22,8 +22,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import java.util.ArrayList; import java.util.List; @@ -42,13 +40,15 @@ import javax.inject.Singleton; public class PreparationCoordinator implements Coordinator { private static final String TAG = "PreparationCoordinator"; - private final NotifLog mNotifLog; + private final PreparationCoordinatorLogger mLogger; private final NotifInflater mNotifInflater; private final List<NotificationEntry> mPendingNotifications = new ArrayList<>(); @Inject - public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) { - mNotifLog = notifLog; + public PreparationCoordinator( + PreparationCoordinatorLogger logger, + NotifInflaterImpl notifInflater) { + mLogger = logger; mNotifInflater = notifInflater; mNotifInflater.setInflationCallback(mInflationCallback); } @@ -106,7 +106,7 @@ public class PreparationCoordinator implements Coordinator { new NotifInflater.InflationCallback() { @Override public void onInflationFinished(NotificationEntry entry) { - mNotifLog.log(NotifEvent.INFLATED, entry); + mLogger.logNotifInflated(entry.getKey()); mPendingNotifications.remove(entry); mNotifInflatingFilter.invalidateList(); } @@ -123,7 +123,7 @@ public class PreparationCoordinator implements Coordinator { } private void abortInflation(NotificationEntry entry, String reason) { - mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason); + mLogger.logInflationAborted(entry.getKey(), reason); entry.abortTask(); mPendingNotifications.remove(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt new file mode 100644 index 000000000000..75e7bc9b79a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -0,0 +1,45 @@ +/* + * 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.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class PreparationCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifInflated(key: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + }, { + "NOTIF INFLATED $str1" + }) + } + + fun logInflationAborted(key: String, reason: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + str2 = reason + }, { + "NOTIF INFLATION ABORTED $str1 reason=$str2" + }) + } +} + +private const val TAG = "PreparationCoordinator"
\ No newline at end of file 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 2a7683a8c7c2..ecf62db4680b 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 @@ -42,8 +42,11 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState 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.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.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -67,8 +70,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private final NotificationGroupManager mGroupManager; private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; + private final Context mContext; - private final NotificationRowContentBinder mRowContentBinder; + private final NotifBindPipeline mNotifBindPipeline; + private final RowContentBindStage mRowContentBindStage; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; @@ -93,7 +98,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Context context, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, - NotificationRowContentBinder rowContentBinder, + NotifBindPipeline notifBindPipeline, + RowContentBindStage rowContentBindStage, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, @@ -103,7 +109,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Provider<RowInflaterTask> rowInflaterTaskProvider, NotificationLogger logger) { mContext = context; - mRowContentBinder = rowContentBinder; + mNotifBindPipeline = notifBindPipeline; + mRowContentBindStage = rowContentBindStage; mMessagingUtil = new NotificationMessagingUtil(context); mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; @@ -167,6 +174,7 @@ 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) { @@ -195,12 +203,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mKeyguardBypassController, mGroupManager, mHeadsUpManager, - mRowContentBinder, + mRowContentBindStage, mPresenter); // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely row.setStatusBarStateController(mStatusBarStateController); - row.setInflationCallback(mInflationCallback); row.setAppOpsOnClickListener(mOnAppOpsClickListener); if (mAllowLongPress) { row.setLongPressListener(mGutsManager::openGuts); @@ -214,6 +221,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } + entry.setRow(row); + row.setEntry(entry); + mNotifBindPipeline.manageRow(entry, row); + mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -247,13 +258,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } } - //TODO: This method associates a row with an entry, but eventually needs to not do that private void updateNotification( NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setIsLowPriority(entry.isAmbient()); // Extract target SDK version. try { @@ -268,22 +277,31 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { // TODO: should updates to the entry be happening somewhere else? entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setRow(row); row.setOnActivatedListener(mPresenter); - boolean useIncreasedCollapsedHeight = + final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, entry.getImportance()); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && !mPresenter.isPresenterFullyCollapsed(); - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.setEntry(entry); + final boolean isLowPriority = entry.isAmbient(); + + RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + params.setUseLowPriority(entry.isAmbient()); if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { - row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); } + //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); - row.inflateViews(); + params.rebindAllContentViews(); + mRowContentBindStage.requestRebind(entry, en -> { + row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.setIsLowPriority(isLowPriority); + mInflationCallback.onAsyncInflationFinished(en); + }); // bind the click event to the content area Objects.requireNonNull(mNotificationClicker).register(row, sbn); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java new file mode 100644 index 000000000000..171816fd28da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.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.collection.notifcollection; + +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A notification collection that manages the list of {@link NotificationEntry}s that will be + * rendered. + * + * TODO: (b/145659174) Once we fully switch off {@link NotificationEntryManager} to + * {@link NotifPipeline}, we probably won't need this, but having it for now makes it easy to + * switch between the two. + */ +public interface CommonNotifCollection { + /** + * Registers a listener to be informed when notifications are created, added, updated, removed, + * or deleted. + */ + void addCollectionListener(NotifCollectionListener listener); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index c7666e47d4b4..39f4dfacc2c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -19,6 +19,10 @@ package com.android.systemui.statusbar.notification.dagger; import android.content.Context; import com.android.systemui.R; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -45,4 +49,16 @@ public class NotificationsModule { return stubController.get(); } } + + /** + * Provide the active notification collection managing the notifications to render. + */ + @Provides + @Singleton + public CommonNotifCollection provideCommonNotifCollection( + FeatureFlags featureFlags, + Lazy<NotifPipeline> pipeline, + NotificationEntryManager entryManager) { + return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager; + } } 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 61e3192eba3c..254b64ffcd90 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 @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -55,6 +56,7 @@ class NotificationsControllerImpl @Inject constructor( private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, private val remoteInputUriController: RemoteInputUriController, @@ -98,6 +100,7 @@ class NotificationsControllerImpl @Inject constructor( if (featureFlags.isNewNotifPipelineRenderingEnabled) { // TODO } else { + notifBindPipelineInitializer.initialize() notificationRowBinder.setInflationCallback(entryManager) remoteInputUriController.attach(entryManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java deleted file mode 100644 index 9adceb78c249..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 com.android.systemui.statusbar.notification.logging; - -import android.annotation.IntDef; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.log.RichEvent; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; -import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An event related to notifications. {@link NotifLog} stores and prints these events for debugging - * and triaging purposes. We do not store a copy of the status bar notification nor ranking - * here to mitigate memory usage. - */ -public class NotifEvent extends RichEvent { - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public NotifEvent init(@EventType int type, StatusBarNotification sbn, - NotificationListenerService.Ranking ranking, String reason) { - StringBuilder extraInfo = new StringBuilder(reason); - if (sbn != null) { - extraInfo.append(" " + sbn.getKey()); - } - - if (ranking != null) { - extraInfo.append(" Ranking="); - extraInfo.append(ranking.getRank()); - } - super.init(INFO, type, extraInfo.toString()); - return this; - } - - /** - * Event labels for ListBuilderEvents - * Index corresponds to an # in {@link EventType} - */ - @Override - public String[] getEventLabels() { - assert (TOTAL_EVENT_LABELS - == (TOTAL_NEM_EVENT_TYPES - + TOTAL_LIST_BUILDER_EVENT_TYPES - + TOTAL_COALESCER_EVENT_TYPES)); - return EVENT_LABELS; - } - - /** - * @return if this event occurred in {@link ShadeListBuilder} - */ - static boolean isListBuilderEvent(@EventType int type) { - return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES); - } - - /** - * @return if this event occurred in {@link NotificationEntryManager} - */ - static boolean isNemEvent(@EventType int type) { - return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES, - TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES); - } - - private static boolean isBetweenInclusive(int x, int a, int b) { - return x >= a && x <= b; - } - - @IntDef({ - // NotifListBuilder events: - WARN, - ON_BUILD_LIST, - START_BUILD_LIST, - DISPATCH_FINAL_LIST, - LIST_BUILD_COMPLETE, - PRE_GROUP_FILTER_INVALIDATED, - PROMOTER_INVALIDATED, - SECTION_INVALIDATED, - COMPARATOR_INVALIDATED, - PARENT_CHANGED, - FILTER_CHANGED, - PROMOTER_CHANGED, - PRE_RENDER_FILTER_INVALIDATED, - - // NotificationEntryManager events: - NOTIF_ADDED, - NOTIF_REMOVED, - NOTIF_UPDATED, - FILTER, - SORT, - FILTER_AND_SORT, - NOTIF_VISIBILITY_CHANGED, - LIFETIME_EXTENDED, - REMOVE_INTERCEPTED, - INFLATION_ABORTED, - INFLATED, - - // GroupCoalescer - COALESCED_EVENT, - EARLY_BATCH_EMIT, - EMIT_EVENT_BATCH - }) - @Retention(RetentionPolicy.SOURCE) - public @interface EventType {} - - private static final String[] EVENT_LABELS = - new String[]{ - // NotifListBuilder labels: - "Warning", - "OnBuildList", - "StartBuildList", - "DispatchFinalList", - "ListBuildComplete", - "FilterInvalidated", - "PromoterInvalidated", - "SectionInvalidated", - "ComparatorInvalidated", - "ParentChanged", - "FilterChanged", - "PromoterChanged", - "FinalFilterInvalidated", - "SectionerChanged", - - // NEM event labels: - "NotifAdded", - "NotifRemoved", - "NotifUpdated", - "Filter", - "Sort", - "FilterAndSort", - "NotifVisibilityChanged", - "LifetimeExtended", - "RemoveIntercepted", - "InflationAborted", - "Inflated", - - // GroupCoalescer labels: - "CoalescedEvent", - "EarlyBatchEmit", - "EmitEventBatch", - "BatchMaxTimeout" - }; - - private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; - - /** - * Events related to {@link ShadeListBuilder} - */ - public static final int WARN = 0; - public static final int ON_BUILD_LIST = 1; - public static final int START_BUILD_LIST = 2; - public static final int DISPATCH_FINAL_LIST = 3; - public static final int LIST_BUILD_COMPLETE = 4; - public static final int PRE_GROUP_FILTER_INVALIDATED = 5; - public static final int PROMOTER_INVALIDATED = 6; - public static final int SECTION_INVALIDATED = 7; - public static final int COMPARATOR_INVALIDATED = 8; - public static final int PARENT_CHANGED = 9; - public static final int FILTER_CHANGED = 10; - public static final int PROMOTER_CHANGED = 11; - public static final int PRE_RENDER_FILTER_INVALIDATED = 12; - public static final int SECTION_CHANGED = 13; - private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14; - - /** - * Events related to {@link NotificationEntryManager} - */ - private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES; - public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX; - public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1; - public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2; - public static final int FILTER = NEM_EVENT_START_INDEX + 3; - public static final int SORT = NEM_EVENT_START_INDEX + 4; - public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5; - public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6; - public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7; - // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor} - public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8; - public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9; - public static final int INFLATED = NEM_EVENT_START_INDEX + 10; - private static final int TOTAL_NEM_EVENT_TYPES = 11; - - /** - * Events related to {@link GroupCoalescer} - */ - private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX - + TOTAL_NEM_EVENT_TYPES; - public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX; - public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1; - public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2; - public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3; - private static final int TOTAL_COALESCER_EVENT_TYPES = 3; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java deleted file mode 100644 index 299d628d0fd2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 com.android.systemui.statusbar.notification.logging; - -import android.os.SystemProperties; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.DumpController; -import com.android.systemui.log.SysuiLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Logs systemui notification events for debugging and triaging purposes. Logs are dumped in - * bugreports or on demand: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController NotifLog - */ -@Singleton -public class NotifLog extends SysuiLog<NotifEvent> { - private static final String TAG = "NotifLog"; - private static final boolean SHOW_NEM_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.nem", true); - private static final boolean SHOW_LIST_BUILDER_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true); - - private static final int MAX_DOZE_DEBUG_LOGS = 400; - private static final int MAX_DOZE_LOGS = 50; - - private NotifEvent mRecycledEvent; - - @Inject - public NotifLog(DumpController dumpController) { - super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS); - } - - /** - * Logs a {@link NotifEvent} with a notification, ranking and message. - * Uses the last recycled event if available. - * @return true if successfully logged, else false - */ - public void log(@NotifEvent.EventType int eventType, - StatusBarNotification sbn, Ranking ranking, String msg) { - if (!mEnabled - || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS) - || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) { - return; - } - - if (mRecycledEvent != null) { - mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg)); - } else { - mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg)); - } - } - - /** - * Logs a {@link NotifEvent} with no extra information aside from the event type - */ - public void log(@NotifEvent.EventType int eventType) { - log(eventType, null, null, ""); - } - - /** - * Logs a {@link NotifEvent} with a message - */ - public void log(@NotifEvent.EventType int eventType, String msg) { - log(eventType, null, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a entry - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) { - log(eventType, entry.getSbn(), entry.getRanking(), ""); - } - - /** - * Logs a {@link NotifEvent} with a NotificationEntry and message - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) { - log(eventType, entry.getSbn(), entry.getRanking(), msg); - } - - /** - * Logs a {@link NotifEvent} with a notification and message - */ - public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) { - log(eventType, sbn, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a ranking and message - */ - public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) { - log(eventType, null, ranking, msg); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt index 88b41471a063..fc221d43b3dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt @@ -93,8 +93,7 @@ class PeopleHubDataSourceImpl @Inject constructor( private val peopleHubManagerForUser = SparseArray<PeopleHubManager>() private val notificationEntryListener = object : NotificationEntryListener { - override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) = - addVisibleEntry(entry) + override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry) override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java new file mode 100644 index 000000000000..1cf6b4f2321c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java @@ -0,0 +1,76 @@ +/* + * 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 androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +/** + * A {@link BindRequester} is a general superclass for something that notifies + * {@link NotifBindPipeline} when it needs it to kick off a bind run. + */ +public abstract class BindRequester { + private @Nullable BindRequestListener mBindRequestListener; + + /** + * Notifies the listener that some parameters/state has changed for some notification and that + * content needs to be bound again. + * + * The caller can also specify a callback for when the entire bind pipeline completes, i.e. + * when the change is fully propagated to the final view. The caller can cancel this + * callback with the returned cancellation signal. + * + * @param callback callback after bind completely finishes + * @return cancellation signal to cancel callback + */ + public final CancellationSignal requestRebind( + @NonNull NotificationEntry entry, + @Nullable BindCallback callback) { + CancellationSignal signal = new CancellationSignal(); + if (mBindRequestListener != null) { + mBindRequestListener.onBindRequest(entry, signal, callback); + } + return signal; + } + + final void setBindRequestListener(BindRequestListener listener) { + mBindRequestListener = listener; + } + + /** + * Listener interface for when content needs to be bound again. + */ + public interface BindRequestListener { + + /** + * Called when {@link #requestRebind} is called. + * + * @param entry notification that has outdated content + * @param signal cancellation signal to cancel callback + * @param callback callback after content is fully updated + */ + void onBindRequest( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback); + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java new file mode 100644 index 000000000000..29447caa1240 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -0,0 +1,104 @@ +/* + * 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 android.annotation.MainThread; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Map; + +/** + * A {@link BindStage} is an abstraction for a unit of work in inflating/binding/unbinding + * views to a notification. Used by {@link NotifBindPipeline}. + * + * Clients may also use {@link #getStageParams} to provide parameters for this stage for a given + * notification and request a rebind. + * + * @param <Params> params to do this stage + */ +@MainThread +public abstract class BindStage<Params> extends BindRequester { + + private Map<NotificationEntry, Params> mContentParams = new ArrayMap<>(); + + /** + * Execute the stage asynchronously. + * + * @param row notification top-level view to bind views to + * @param callback callback after stage finishes + */ + protected abstract void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback); + + /** + * Abort the stage if in progress. + * + * @param row notification top-level view to bind views to + */ + protected abstract void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row); + + /** + * Get the stage parameters for the entry. Clients should use this to modify how the stage + * handles the notification content. + */ + public final Params getStageParams(@NonNull NotificationEntry entry) { + Params params = mContentParams.get(entry); + if (params == null) { + throw new IllegalStateException( + String.format("Entry does not have any stage parameters. key: %s", + entry.getKey())); + } + return params; + } + + /** + * Create a params entry for the notification for this stage. + */ + final void createStageParams(@NonNull NotificationEntry entry) { + mContentParams.put(entry, newStageParams()); + } + + /** + * Delete params entry for notification. + */ + final void deleteStageParams(@NonNull NotificationEntry entry) { + mContentParams.remove(entry); + } + + /** + * Create a new, empty stage params object. + */ + protected abstract Params newStageParams(); + + /** + * Interface for callback. + */ + interface StageCallback { + /** + * Callback for when the stage is complete. + */ + void onStageFinished(NotificationEntry entry); + } +} 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 253be2fcb5ca..c34bba782ad5 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 @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -88,8 +86,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationCounters; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -127,14 +123,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - /** - * Content views that must be inflated at all times. - */ - @InflationFlag - static final int REQUIRED_INFLATION_FLAGS = - FLAG_CONTENT_VIEW_CONTRACTED - | FLAG_CONTENT_VIEW_EXPANDED; - private boolean mUpdateBackgroundOnUpdate; private boolean mNotificationTranslationFinished = false; @@ -149,7 +137,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private NotificationRowContentBinder mNotificationContentBinder; + private RowContentBindStage mRowContentBindStage; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -244,10 +232,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnAppOpsClickListener; - private InflationCallback mInflationCallback; private boolean mIsChildInGroup; - private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS; - private final BindParams mBindParams = new BindParams(); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -460,24 +445,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Inflate views based off the inflation flags set. Inflation happens asynchronously. - */ - public void inflateViews() { - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - false /* forceInflate */, mInflationCallback); - } - - /** * Marks a content view as freeable, setting it so that future inflations do not reinflate * and ensuring that the view is freed when it is safe to remove. * + * TODO: This should be moved to the respective coordinator and call + * {@link RowContentBindParams#freeContentViews} directly after disappear animation + * finishes instead of depending on binding API to know when it's "safe". + * * @param inflationFlag flag corresponding to the content view to be freed */ public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { // View should not be reinflated in the future - clearInflationFlags(inflationFlag); - Runnable freeViewRunnable = - () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); + Runnable freeViewRunnable = () -> { + // Possible for notification to be removed after free request. + if (!isRemoved()) { + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.freeContentViews(inflationFlag); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); + } + }; switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -492,35 +478,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Set flags for content views that should be inflated - * - * @param flags flags to inflate - */ - public void setInflationFlags(@InflationFlag int flags) { - mInflationFlags |= flags; - } - - /** - * Clear flags for content views that should not be inflated - * - * @param flags flags that should not be inflated - */ - public void clearInflationFlags(@InflationFlag int flags) { - mInflationFlags &= ~flags; - mInflationFlags |= REQUIRED_INFLATION_FLAGS; - } - - /** - * Whether or not a content view should be inflated. - * - * @param flag the flag corresponding to the content view - * @return true if the flag is set, false otherwise - */ - public boolean isInflationFlagSet(@InflationFlag int flag) { - return ((mInflationFlags & flag) != 0); - } - - /** * Caches whether or not this row contains a system notification. Note, this is only cached * once per notification as the packageInfo can't technically change for a notification row. */ @@ -838,13 +795,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } mNotificationParent = isChildInGroup ? parent : null; mPrivateLayout.setIsChildInGroup(isChildInGroup); - mBindParams.isChildInGroup = isChildInGroup; + // TODO: Move inflation logic out of this call if (mIsChildInGroup != isChildInGroup) { mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { - int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, - false /* forceInflate */, mInflationCallback); + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setUseLowPriority(mIsLowPriority); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } resetBackgroundAlpha(); @@ -1243,8 +1200,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - true /* forceInflate */, mInflationCallback); + // TODO: Move content inflation logic out of this call + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setNeedsReinflation(true); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } @Override @@ -1598,7 +1557,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; mPrivateLayout.setIsLowPriority(isLowPriority); - mBindParams.isLowPriority = mIsLowPriority; if (mChildrenContainer != null) { mChildrenContainer.setIsLowPriority(isLowPriority); } @@ -1608,36 +1566,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsLowPriority; } - public void setUseIncreasedCollapsedHeight(boolean use) { + public void setUsesIncreasedCollapsedHeight(boolean use) { mUseIncreasedCollapsedHeight = use; - mBindParams.usesIncreasedHeight = use; } - public void setUseIncreasedHeadsUpHeight(boolean use) { + public void setUsesIncreasedHeadsUpHeight(boolean use) { mUseIncreasedHeadsUpHeight = use; - mBindParams.usesIncreasedHeadsUpHeight = use; - } - - /** - * Set callback for notification content inflation - * - * @param callback inflation callback - */ - public void setInflationCallback(InflationCallback callback) { - mInflationCallback = callback; } public void setNeedsRedaction(boolean needsRedaction) { + // TODO: Move inflation logic out of this call and remove this method if (mNeedsRedaction != needsRedaction) { mNeedsRedaction = needsRedaction; + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); if (needsRedaction) { - setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, - mBindParams, false /* forceInflate */, mInflationCallback); + params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { - clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC); + params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC); } + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } @@ -1664,7 +1611,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView KeyguardBypassController bypassController, NotificationGroupManager groupManager, HeadsUpManager headsUpManager, - NotificationRowContentBinder rowContentBinder, + RowContentBindStage rowContentBindStage, OnExpandClickListener onExpandClickListener) { mAppName = appName; if (mMenuRow != null && mMenuRow.getMenuView() != null) { @@ -1676,7 +1623,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mGroupManager = groupManager; mPrivateLayout.setGroupManager(groupManager); mHeadsUpManager = headsUpManager; - mNotificationContentBinder = rowContentBinder; + mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java new file mode 100644 index 000000000000..af2d0844412a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -0,0 +1,207 @@ +/* + * 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 android.util.ArrayMap; +import android.util.ArraySet; +import android.widget.FrameLayout; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * {@link NotifBindPipeline} is responsible for converting notifications from their data form to + * their actual inflated views. It is essentially a control class that composes notification view + * binding logic (i.e. {@link BindStage}) in response to explicit bind requests. At the end of the + * pipeline, the notification's bound views are guaranteed to be correct and up-to-date, and any + * registered callbacks will be called. + * + * The pipeline ensures that a notification's top-level view and its content views are bound. + * Currently, a notification's top-level view, the {@link ExpandableNotificationRow} is essentially + * just a {@link FrameLayout} for various different content views that are switched in and out as + * appropriate. These include a contracted view, expanded view, heads up view, and sensitive view on + * keyguard. See {@link InflationFlag}. These content views themselves can have child views added + * on depending on different factors. For example, notification actions and smart replies are views + * that are dynamically added to these content views after they're inflated. Finally, aside from + * the app provided content views, System UI itself also provides some content views that are shown + * occasionally (e.g. {@link NotificationGuts}). Many of these are business logic specific views + * and the requirements surrounding them may change over time, so the pipeline must handle + * composing the logic as necessary. + * + * Note that bind requests do not only occur from add/updates from updates from the app. For + * example, the user may make changes to device settings (e.g. sensitive notifications on lock + * screen) or we may want to make certain optimizations for the sake of memory or performance (e.g + * freeing views when not visible). Oftentimes, we also need to wait for these changes to complete + * before doing something else (e.g. moving a notification to the top of the screen to heads up). + * The pipeline thus handles bind requests from across the system and provides a way for + * requesters to know when the change is propagated to the view. + * + * Right now, we only support one attached {@link BindStage} which just does all the binding but we + * should eventually support multiple stages once content inflation is made more modular. + * In particular, row inflation/binding, which is handled by {@link NotificationRowBinder} should + * probably be moved here in the future as a stage. Right now, the pipeline just manages content + * views and assumes that a row is given to it when it's inflated. + */ +@MainThread +@Singleton +public final class NotifBindPipeline { + private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); + private BindStage mStage; + + @Inject + NotifBindPipeline(NotificationEntryManager entryManager) { + entryManager.addNotificationEntryListener(mEntryListener); + } + + /** + * Set the bind stage for binding notification row content. + */ + public void setStage( + BindStage stage) { + mStage = stage; + mStage.setBindRequestListener(this::onBindRequested); + } + + /** + * Start managing the row's content for a given notification. + */ + public void manageRow( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + final BindEntry bindEntry = getBindEntry(entry); + bindEntry.row = row; + if (bindEntry.invalidated) { + startPipeline(entry); + } + } + + private void onBindRequested( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback) { + final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry == null) { + // Invalidating views for a notification that is not active. + return; + } + + bindEntry.invalidated = true; + + // Put in new callback. + if (callback != null) { + final Set<BindCallback> callbacks = bindEntry.callbacks; + callbacks.add(callback); + signal.setOnCancelListener(() -> callbacks.remove(callback)); + } + + startPipeline(entry); + } + + /** + * Run the pipeline for the notification, ensuring all views are bound when finished. Call all + * callbacks when the run finishes. If a run is already in progress, it is restarted. + */ + private void startPipeline(NotificationEntry entry) { + if (mStage == null) { + throw new IllegalStateException("No stage was ever set on the pipeline"); + } + + final BindEntry bindEntry = mBindEntries.get(entry); + final ExpandableNotificationRow row = bindEntry.row; + if (row == null) { + // Row is not managed yet but may be soon. Stop for now. + return; + } + + mStage.abortStage(entry, row); + mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); + } + + private void onPipelineComplete(NotificationEntry entry) { + final BindEntry bindEntry = getBindEntry(entry); + + bindEntry.invalidated = false; + + final Set<BindCallback> callbacks = bindEntry.callbacks; + for (BindCallback cb : callbacks) { + cb.onBindFinished(entry); + } + callbacks.clear(); + } + + //TODO: Move this to onManageEntry hook when we split that from add/remove + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + mBindEntries.put(entry, new BindEntry()); + mStage.createStageParams(entry); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + BindEntry bindEntry = mBindEntries.remove(entry); + ExpandableNotificationRow row = bindEntry.row; + if (row != null) { + mStage.abortStage(entry, row); + } + mStage.deleteStageParams(entry); + } + }; + + private @NonNull BindEntry getBindEntry(NotificationEntry entry) { + final BindEntry bindEntry = mBindEntries.get(entry); + if (bindEntry == null) { + throw new IllegalStateException( + String.format("Attempting bind on an inactive notification. key: %s", + entry.getKey())); + } + return bindEntry; + } + + /** + * Interface for bind callback. + */ + public interface BindCallback { + /** + * Called when all views are fully bound on the notification. + */ + void onBindFinished(NotificationEntry entry); + } + + private class BindEntry { + public ExpandableNotificationRow row; + public final Set<BindCallback> callbacks = new ArraySet<>(); + public boolean invalidated; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java new file mode 100644 index 000000000000..7754991557ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Initialize {@link NotifBindPipeline} with all its mandatory stages and dynamically added stages. + * + * In the future, coordinators should be able to register their own {@link BindStage} to the + * {@link NotifBindPipeline}. + */ +public class NotifBindPipelineInitializer { + NotifBindPipeline mNotifBindPipeline; + RowContentBindStage mRowContentBindStage; + + @Inject + NotifBindPipelineInitializer( + NotifBindPipeline pipeline, + RowContentBindStage stage) { + mNotifBindPipeline = pipeline; + mRowContentBindStage = stage; + // TODO: Inject coordinators and allow them to add BindStages in initialize + } + + /** + * Hooks up stages to the pipeline. + */ + public void initialize() { + // Mandatory bind stages + mNotifBindPipeline.setStage(mRowContentBindStage); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java index a6e5c2b79968..b62dfa8a20f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java @@ -22,10 +22,9 @@ import android.widget.RemoteViews; import androidx.annotation.Nullable; -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import java.util.Map; @@ -40,8 +39,8 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { new ArrayMap<>(); @Inject - NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { - entryManager.addNotificationEntryListener(mEntryListener); + NotifRemoteViewCacheImpl(CommonNotifCollection collection) { + collection.addCollectionListener(mCollectionListener); } @Override @@ -93,17 +92,14 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { contentViews.clear(); } - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @Override - public void onPendingEntryAdded(NotificationEntry entry) { + public void onEntryInit(NotificationEntry entry) { mNotifCachedContentViews.put(entry, new SparseArray<>()); } @Override - public void onEntryRemoved( - NotificationEntry entry, - @Nullable NotificationVisibility visibility, - boolean removedByUser) { + public void onEntryCleanUp(NotificationEntry entry) { mNotifCachedContentViews.remove(entry); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e1a6747b5398..566da65e37f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -68,7 +68,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotifRemoteViewCache mRemoteViewCache; @Inject - public NotificationContentInflater( + NotificationContentInflater( NotifRemoteViewCache remoteViewCache, NotificationRemoteInputManager remoteInputManager) { mRemoteViewCache = remoteViewCache; @@ -575,7 +575,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(entry, reInflateFlags); + endListener.onAsyncInflationFinished(entry); } return true; } @@ -641,7 +641,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final boolean mUsesIncreasedHeight; private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; - private @InflationFlag int mReInflateFlags; + private final @InflationFlag int mReInflateFlags; private final NotifRemoteViewCache mRemoteViewCache; private ExpandableNotificationRow mRow; private Exception mError; @@ -739,25 +739,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder } @Override - public void supersedeTask(InflationTask task) { - if (task instanceof AsyncInflationTask) { - // We want to inflate all flags of the previous task as well - mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; - } - } - - @Override public void handleInflationException(NotificationEntry entry, Exception e) { handleError(e); } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mEntry.onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); + mCallback.onAsyncInflationFinished(mEntry); } // Notify the resolver that the inflation task has finished, 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 6045524f30b6..bb0681ce8a44 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 @@ -280,7 +280,7 @@ public class NotificationConversationInfo extends LinearLayout implements Button favorite = findViewById(R.id.fave); favorite.setOnClickListener(mOnFavoriteClick); - if (mNotificationChannel.canBypassDnd()) { + if (mNotificationChannel.isImportantConversation()) { favorite.setText(R.string.notification_conversation_unfavorite); favorite.setCompoundDrawablesRelative( mContext.getDrawable(R.drawable.ic_star), null, null, null); @@ -621,8 +621,8 @@ public class NotificationConversationInfo extends LinearLayout implements } break; case ACTION_FAVORITE: - // TODO: extend beyond DND - mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd()); + mChannelToUpdate.setImportantConversation( + !mChannelToUpdate.isImportantConversation()); break; case ACTION_MUTE: if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 9b95bff4921c..9bd8d4782672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -101,7 +101,7 @@ public interface NotificationRowContentBinder { */ int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3; - int FLAG_CONTENT_VIEW_ALL = ~0; + int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1; /** * Parameters for content view binding @@ -146,9 +146,7 @@ public interface NotificationRowContentBinder { * Callback for after the content views finish inflating. * * @param entry the entry with the content views set - * @param inflatedFlags the flags associated with the content views that were inflated */ - void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags); + void onAsyncInflationFinished(NotificationEntry entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java new file mode 100644 index 000000000000..5170d0b85b17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -0,0 +1,165 @@ +/* + * 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.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +/** + * Parameters for {@link RowContentBindStage}. + */ +public final class RowContentBindParams { + private boolean mUseLowPriority; + private boolean mUseChildInGroup; + private boolean mUseIncreasedHeight; + private boolean mUseIncreasedHeadsUpHeight; + private boolean mViewsNeedReinflation; + private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS; + + /** + * Content views that are out of date and need to be rebound. + * + * TODO: This should go away once {@link NotificationContentInflater} is broken down into + * smaller stages as then the stage itself would be invalidated. + */ + private @InflationFlag int mDirtyContentViews = mContentViews; + + /** + * Set whether content should use a low priority version of its content views. + */ + public void setUseLowPriority(boolean useLowPriority) { + if (mUseLowPriority != useLowPriority) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseLowPriority = useLowPriority; + } + + public boolean useLowPriority() { + return mUseLowPriority; + } + + /** + * Set whether content should use group child version of its content views. + */ + public void setUseChildInGroup(boolean useChildInGroup) { + if (mUseChildInGroup != useChildInGroup) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseChildInGroup = useChildInGroup; + } + + public boolean useChildInGroup() { + return mUseChildInGroup; + } + + /** + * Set whether content should use an increased height version of its contracted view. + */ + public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) { + if (mUseIncreasedHeight != useIncreasedHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED; + } + mUseIncreasedHeight = useIncreasedHeight; + } + + public boolean useIncreasedHeight() { + return mUseIncreasedHeight; + } + + /** + * Set whether content should use an increased height version of its heads up view. + */ + public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) { + if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP; + } + mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight; + } + + public boolean useIncreasedHeadsUpHeight() { + return mUseIncreasedHeadsUpHeight; + } + + /** + * Require the specified content views to be bound after the rebind request. + * + * @see InflationFlag + */ + public void requireContentViews(@InflationFlag int contentViews) { + @InflationFlag int newContentViews = contentViews &= ~mContentViews; + mContentViews |= contentViews; + mDirtyContentViews |= newContentViews; + } + + /** + * Free the content view so that it will no longer be bound after the rebind request. + * + * @see InflationFlag + */ + public void freeContentViews(@InflationFlag int contentViews) { + mContentViews &= ~contentViews; + mDirtyContentViews &= ~contentViews; + } + + public @InflationFlag int getContentViews() { + return mContentViews; + } + + /** + * Request that all content views be rebound. This may happen if, for example, the underlying + * layout has changed. + */ + public void rebindAllContentViews() { + mDirtyContentViews = mContentViews; + } + + /** + * Clears all dirty content views so that they no longer need to be rebound. + */ + void clearDirtyContentViews() { + mDirtyContentViews = 0; + } + + public @InflationFlag int getDirtyContentViews() { + return mDirtyContentViews; + } + + /** + * Set whether all content views need to be reinflated even if cached. + * + * TODO: This should probably be a more global config on {@link NotifBindPipeline} since this + * generally corresponds to a Context/Configuration change that all stages should know about. + */ + public void setNeedsReinflation(boolean needsReinflation) { + mViewsNeedReinflation = needsReinflation; + @InflationFlag int currentContentViews = mContentViews; + mDirtyContentViews |= currentContentViews; + } + + public boolean needsReinflation() { + return mViewsNeedReinflation; + } + + /** + * Content views that should be inflated by default for all notifications. + */ + @InflationFlag private static final int DEFAULT_INFLATION_FLAGS = + FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java new file mode 100644 index 000000000000..f1241799d3d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -0,0 +1,118 @@ +/* + * 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.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; + +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; + +import androidx.annotation.NonNull; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}. + * + * In the farther future, the binder logic and consequently this stage should be broken into + * smaller stages. + */ +@Singleton +public class RowContentBindStage extends BindStage<RowContentBindParams> { + private final NotificationRowContentBinder mBinder; + private final IStatusBarService mStatusBarService; + + @Inject + RowContentBindStage( + NotificationRowContentBinder binder, + IStatusBarService statusBarService) { + mBinder = binder; + mStatusBarService = statusBarService; + } + + @Override + protected void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback) { + RowContentBindParams params = getStageParams(entry); + + // Resolve content to bind/unbind. + @InflationFlag int inflationFlags = params.getContentViews(); + @InflationFlag int invalidatedFlags = params.getDirtyContentViews(); + + @InflationFlag int contentToBind = invalidatedFlags & inflationFlags; + @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL; + + // Bind/unbind with parameters + mBinder.unbindContent(entry, row, contentToUnbind); + + BindParams bindParams = new BindParams(); + bindParams.isLowPriority = params.useLowPriority(); + bindParams.isChildInGroup = params.useChildInGroup(); + bindParams.usesIncreasedHeight = params.useIncreasedHeight(); + bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); + boolean forceInflate = params.needsReinflation(); + + InflationCallback inflationCallback = new InflationCallback() { + @Override + public void handleInflationException(NotificationEntry entry, Exception e) { + entry.setHasInflationError(true); + try { + final StatusBarNotification sbn = entry.getSbn(); + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + e.getMessage(), + sbn.getUserId()); + } catch (RemoteException ex) { + } + } + + @Override + public void onAsyncInflationFinished(NotificationEntry entry) { + entry.setHasInflationError(false); + getStageParams(entry).clearDirtyContentViews(); + callback.onStageFinished(entry); + } + }; + mBinder.cancelBind(entry, row); + mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback); + } + + @Override + protected void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + mBinder.cancelBind(entry, row); + } + + @Override + protected RowContentBindParams newStageParams() { + return new RowContentBindParams(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 896b6e570da2..bdca9a452484 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -28,11 +28,12 @@ import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.AlertingNotificationManager; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -67,6 +68,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>(); private HeadsUpManager mHeadsUpManager; + private final RowContentBindStage mRowContentBindStage; private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); @@ -75,8 +77,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private boolean mIsDozing; @Inject - public NotificationGroupAlertTransferHelper() { + public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) { Dependency.get(StatusBarStateController.class).addCallback(this); + mRowContentBindStage = bindStage; } /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ @@ -190,21 +193,6 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } } - // Called when the entry's reinflation has finished. If there is an alert pending, we - // then show the alert. - @Override - public void onEntryReinflated(NotificationEntry entry) { - PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); - if (alertInfo != null) { - if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, mHeadsUpManager); - } else { - // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); - } - } - } - @Override public void onEntryRemoved( @Nullable NotificationEntry entry, @@ -392,10 +380,21 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void alertNotificationWhenPossible(@NonNull NotificationEntry entry, @NonNull AlertingNotificationManager alertManager) { @InflationFlag int contentFlag = alertManager.getContentFlag(); - if (!entry.getRow().isInflationFlagSet(contentFlag)) { + final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + if ((params.getContentViews() & contentFlag) == 0) { mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); - entry.getRow().setInflationFlags(contentFlag); - entry.getRow().inflateViews(); + params.requireContentViews(contentFlag); + mRowContentBindStage.requestRebind(entry, en -> { + PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); + if (alertInfo != null) { + if (alertInfo.isStillValid()) { + alertNotificationWhenPossible(entry, mHeadsUpManager); + } else { + // The transfer is no longer valid. Free the content. + entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); + } + } + }); return; } if (alertManager.isAlerting(entry.getKey())) { @@ -426,9 +425,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis /** * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer - * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead - * we just flag it as aborted and free when it's inflated. + * back). + * + * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}. */ boolean mAbortOnInflation; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 2907cd41a0db..8dfcb0ac215d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -134,6 +134,8 @@ public class UserSwitcherController implements Dumpable { mBroadcastDispatcher.registerReceiver( mReceiver, filter, null /* handler */, UserHandle.SYSTEM); + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); + mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); filter = new IntentFilter(); @@ -258,22 +260,20 @@ public class UserSwitcherController implements Dumpable { && mUserManager.canAddMoreUsers(); boolean createIsRestricted = !addUsersWhenLocked; - if (!mSimpleUserSwitcher) { - if (guestRecord == null) { - if (canCreateGuest) { - guestRecord = new UserRecord(null /* info */, null /* picture */, - true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, createIsRestricted, canSwitchUsers); - checkIfAddUserDisallowedByAdminOnly(guestRecord); - records.add(guestRecord); - } - } else { - int index = guestRecord.isCurrent ? 0 : records.size(); - records.add(index, guestRecord); + if (guestRecord == null) { + if (canCreateGuest) { + guestRecord = new UserRecord(null /* info */, null /* picture */, + true /* isGuest */, false /* isCurrent */, + false /* isAddUser */, createIsRestricted, canSwitchUsers); + checkIfAddUserDisallowedByAdminOnly(guestRecord); + records.add(guestRecord); } + } else { + int index = guestRecord.isCurrent ? 0 : records.size(); + records.add(index, guestRecord); } - if (!mSimpleUserSwitcher && canCreateUser) { + if (canCreateUser) { UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, false /* isGuest */, false /* isCurrent */, true /* isAddUser */, createIsRestricted, canSwitchUsers); @@ -562,8 +562,7 @@ public class UserSwitcherController implements Dumpable { private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { public void onChange(boolean selfChange) { - mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), - SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; refreshUsers(UserHandle.USER_NULL); @@ -579,6 +578,7 @@ public class UserSwitcherController implements Dumpable { final UserRecord u = mUsers.get(i); pw.print(" "); pw.println(u.toString()); } + pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher); } public String getCurrentUserName(Context context) { @@ -717,6 +717,13 @@ public class UserSwitcherController implements Dumpable { } } + private boolean shouldUseSimpleUserSwitcher() { + int defaultSimpleUserSwitcher = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0; + return Settings.Global.getInt(mContext.getContentResolver(), + SIMPLE_USER_SWITCHER_GLOBAL_SETTING, defaultSimpleUserSwitcher) != 0; + } + public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java index a60ca6201419..49ada1a5e41e 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java @@ -158,7 +158,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference String demoTime = "1010"; // 10:10, a classic choice of horologists try { - String[] versionParts = android.os.Build.VERSION.RELEASE.split("\\."); + String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\."); int majorVersion = Integer.valueOf(versionParts[0]); demoTime = String.format("%02d00", majorVersion % 24); } catch (IllegalArgumentException ex) { diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt new file mode 100644 index 000000000000..70bcc21426b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt @@ -0,0 +1,320 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.util.Log +import com.android.systemui.util.FloatingContentCoordinator.FloatingContent +import java.util.HashMap +import javax.inject.Inject +import javax.inject.Singleton + +/** Tag for debug logging. */ +private const val TAG = "FloatingCoordinator" + +/** + * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure + * that they don't overlap. If content does overlap due to content appearing or moving, the + * coordinator will ask content to move to resolve the conflict. + * + * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination. + * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move + * other content out of the way. [onContentRemoved] should be called when the content is removed or + * no longer visible. + */ +@Singleton +class FloatingContentCoordinator @Inject constructor() { + + /** + * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods + * that allow the [FloatingContentCoordinator] to determine the current location of the content, + * as well as the ability to ask it to move out of the way of other content. + * + * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down, + * depending on the position of the conflicting content. You can override this method if you + * want your own custom conflict resolution logic. + */ + interface FloatingContent { + + /** + * Return the bounds claimed by this content. This should include the bounds occupied by the + * content itself, as well as any padding, if desired. The coordinator will ensure that no + * other content is located within these bounds. + * + * If the content is animating, this method should return the bounds to which the content is + * animating. If that animation is cancelled, or updated, be sure that your implementation + * of this method returns the appropriate bounds, and call [onContentMoved] so that the + * coordinator moves other content out of the way. + */ + fun getFloatingBoundsOnScreen(): Rect + + /** + * Return the area within which this floating content is allowed to move. When resolving + * conflicts, the coordinator will never ask your content to move to a position where any + * part of the content would be out of these bounds. + */ + fun getAllowedFloatingBoundsRegion(): Rect + + /** + * Called when the coordinator needs this content to move to the given bounds. It's up to + * you how to do that. + * + * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should + * return the destination bounds, not the in-progress animated bounds. This is so the + * coordinator knows where floating content is going to be and can resolve conflicts + * accordingly. + */ + fun moveToBounds(bounds: Rect) + + /** + * Called by the coordinator when it needs to find a new home for this floating content, + * because a new or moving piece of content is now overlapping with it. + * + * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility + * functions that will find new bounds for your content automatically. Unless you require + * specific conflict resolution logic, these should be sufficient. By default, this method + * delegates to [findAreaForContentVertically]. + * + * @param overlappingContentBounds The bounds of the other piece of content, which + * necessitated this content's relocation. Your new position must not overlap with these + * bounds. + * @param otherContentBounds The bounds of any other pieces of floating content. Your new + * position must not overlap with any of these either. These bounds are guaranteed to be + * non-overlapping. + * @return The new bounds for this content. + */ + @JvmDefault + fun calculateNewBoundsOnOverlap( + overlappingContentBounds: Rect, + otherContentBounds: List<Rect> + ): Rect { + return findAreaForContentVertically( + getFloatingBoundsOnScreen(), + overlappingContentBounds, + otherContentBounds, + getAllowedFloatingBoundsRegion()) + } + } + + /** The bounds of all pieces of floating content added to the coordinator. */ + private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap() + + /** + * Makes the coordinator aware of a new piece of floating content, and moves any existing + * content out of the way, if necessary. + * + * If you don't want your new content to move existing content, use [getOccupiedBounds] to find + * an unoccupied area, and move the content there before calling this method. + */ + fun onContentAdded(newContent: FloatingContent) { + updateContentBounds() + allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen() + maybeMoveConflictingContent(newContent) + } + + /** + * Called to notify the coordinator that a piece of floating content has moved (or is animating) + * to a new position, and that any conflicting floating content should be moved out of the way. + * + * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds + * for the moving content. If you're animating the content, be sure that your implementation of + * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's + * current bounds. + * + * If the animation moving this content is cancelled or updated, you'll need to call this method + * again, to ensure that content is moved out of the way of the latest bounds. + * + * @param content The content that has moved. + */ + @JvmOverloads + fun onContentMoved(content: FloatingContent) { + if (!allContentBounds.containsKey(content)) { + Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " + + "This should never happen.") + return + } + + updateContentBounds() + maybeMoveConflictingContent(content) + } + + /** + * Called to notify the coordinator that a piece of floating content has been removed or is no + * longer visible. + */ + fun onContentRemoved(removedContent: FloatingContent) { + allContentBounds.remove(removedContent) + } + + /** + * Returns a set of Rects that represent the bounds of all of the floating content on the + * screen. + * + * [onContentAdded] will move existing content out of the way if the added content intersects + * existing content. That's fine - but if your specific starting position is not important, you + * can use this function to find unoccupied space for your content before calling + * [onContentAdded], so that moving existing content isn't necessary. + */ + fun getOccupiedBounds(): Collection<Rect> { + return allContentBounds.values + } + + /** + * Identifies any pieces of content that are now overlapping with the given content, and asks + * them to move out of the way. + */ + private fun maybeMoveConflictingContent(fromContent: FloatingContent) { + val conflictingNewBounds = allContentBounds[fromContent]!! + allContentBounds + // Filter to content that intersects with the new bounds. That's content that needs + // to move. + .filter { (content, bounds) -> + content != fromContent && Rect.intersects(conflictingNewBounds, bounds) } + // Tell that content to get out of the way, and save the bounds it says it's moving + // (or animating) to. + .forEach { (content, bounds) -> + content.moveToBounds( + content.calculateNewBoundsOnOverlap( + conflictingNewBounds, + // Pass all of the content bounds except the bounds of the + // content we're asking to move, and the conflicting new bounds + // (since those are passed separately). + otherContentBounds = allContentBounds.values + .minus(bounds) + .minus(conflictingNewBounds))) + allContentBounds[content] = content.getFloatingBoundsOnScreen() + } + } + + /** + * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all + * content and saving the result. + */ + private fun updateContentBounds() { + allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() } + } + + companion object { + /** + * Finds new bounds for the given content, either above or below its current position. The + * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and + * will be within the allowed bounds unless no possible position exists. + * + * You can use this method to help find a new position for your content when the coordinator + * calls [FloatingContent.moveToAreaExcluding]. + * + * @param contentRect The bounds of the content for which we're finding a new home. + * @param newlyOverlappingRect The bounds of the content that forced this relocation by + * intersecting with the content we now need to move. If the overlapping content is + * overlapping the top half of this content, we'll try to move this content downward if + * possible (since the other content is 'pushing' it down), and vice versa. + * @param exclusionRects Any other areas that we need to avoid when finding a new home for + * the content. These areas must be non-overlapping with each other. + * @param allowedBounds The area within which we're allowed to find new bounds for the + * content. + * @return New bounds for the content that don't intersect the exclusion rects or the + * newly overlapping rect, and that is within bounds unless no possible in-bounds position + * exists. + */ + @JvmStatic + fun findAreaForContentVertically( + contentRect: Rect, + newlyOverlappingRect: Rect, + exclusionRects: Collection<Rect>, + allowedBounds: Rect + ): Rect { + // If the newly overlapping Rect's center is above the content's center, we'll prefer to + // find a space for this content that is below the overlapping content, since it's + // 'pushing' it down. This may not be possible due to to screen bounds, in which case + // we'll find space in the other direction. + val overlappingContentPushingDown = + newlyOverlappingRect.centerY() < contentRect.centerY() + + // Filter to exclusion rects that are above or below the content that we're finding a + // place for. Then, split into two lists - rects above the content, and rects below it. + var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects + .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) } + .partition { rectToAvoid -> rectToAvoid.top < contentRect.top } + + // Lazily calculate the closest possible new tops for the content, above and below its + // current location. + val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect), + findAbove = true) } + val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect), + findAbove = false) } + + val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) } + val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) } + + // Use the 'below' position if the content is being overlapped from the top, unless it's + // out of bounds. Also use it if the content is being overlapped from the bottom, but + // the 'above' position is out of bounds. Otherwise, use the 'above' position. + val usePositionBelow = + overlappingContentPushingDown && positionBelowInBounds || + !overlappingContentPushingDown && !positionAboveInBounds + + // Return the content rect, but offset to reflect the new position. + return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove + } + + /** + * Finds a new position for the given content, either above or below its current position + * depending on whether [findAbove] is true or false, respectively. This new position will + * not intersect with any of the [exclusionRects]. + * + * This method is useful as a helper method for implementing your own conflict resolution + * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen + * bounds and conflicting bounds' location into account when deciding whether to move to new + * bounds above or below the current bounds. + * + * @param contentRect The content we're finding an area for. + * @param exclusionRects The areas we need to avoid when finding a new area for the content. + * These areas must be non-overlapping with each other. + * @param findAbove Whether we are finding an area above the content's current position, + * rather than an area below it. + */ + fun findAreaForContentAboveOrBelow( + contentRect: Rect, + exclusionRects: Collection<Rect>, + findAbove: Boolean + ): Rect { + // Sort the rects, since we want to move the content as little as possible. We'll + // start with the rects closest to the content and move outward. If we're finding an + // area above the content, that means we sort in reverse order to search the rects + // from highest to lowest y-value. + val sortedExclusionRects = + exclusionRects.sortedBy { if (findAbove) -it.top else it.top } + + val proposedNewBounds = Rect(contentRect) + for (exclusionRect in sortedExclusionRects) { + // If the proposed new bounds don't intersect with this exclusion rect, that + // means there's room for the content here. We know this because the rects are + // sorted and non-overlapping, so any subsequent exclusion rects would be higher + // (or lower) than this one and can't possibly intersect if this one doesn't. + if (!Rect.intersects(proposedNewBounds, exclusionRect)) { + break + } else { + // Otherwise, we need to keep searching for new bounds. If we're finding an + // area above, propose new bounds that place the content just above the + // exclusion rect. If we're finding an area below, propose new bounds that + // place the content just below the exclusion rect. + val verticalOffset = + if (findAbove) -contentRect.height() else exclusionRect.height() + proposedNewBounds.offsetTo( + proposedNewBounds.left, + exclusionRect.top + verticalOffset) + } + } + + return proposedNewBounds + } + + /** Returns whether or not the two Rects share any of the same space on the X axis. */ + private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean { + return (r1.left >= r2.left && r1.left <= r2.right) || + (r1.right <= r2.right && r1.right >= r2.left) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 1954b3936376..0e9a245d5be6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -39,7 +39,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceView; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -54,7 +54,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; @RunWithLooper @RunWith(AndroidTestingRunner.class) @@ -77,8 +76,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private KeyguardSecurityCallback mKeyguardCallback; @Mock private KeyguardUpdateMonitor mUpdateMonitor; - @Spy - private StubTransaction mTransaction; + @Mock + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Before public void setUp() { @@ -97,21 +96,20 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient); mTestController = new AdminSecondaryLockScreenController( - mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler, mTransaction); + mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler); } @Test public void testShow() throws Exception { doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); mTestController.show(mServiceIntent); verifySurfaceReady(); - verify(mTransaction).reparent(any(), any()); assertThat(mContext.isBound(mComponentName)).isTrue(); } @@ -133,7 +131,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { // Show the view first, then hide. doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); @@ -189,19 +187,4 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID); assertThat(mContext.isBound(mComponentName)).isFalse(); } - - /** - * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit testing to - * avoid calls to native code. - */ - private class StubTransaction extends SurfaceControl.Transaction { - @Override - public void apply() { - } - - @Override - public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) { - return this; - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 364ee666e17d..ffe8c285b4f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -31,8 +31,8 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index f2642594802d..c6c7b87da544 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -36,6 +36,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; +import android.os.IBinder; import android.os.UserManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -43,6 +44,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ScrollView; @@ -175,6 +177,28 @@ public class AuthContainerViewTest extends SysuiTestCase { assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType); } + @Test + public void testCredentialUI_disablesClickingOnBackground() { + // In the credential view, clicking on the background (to cancel authentication) is not + // valid. Thus, the listener should be null, and it should not be in the accessibility + // hierarchy. + initializeContainer(Authenticators.DEVICE_CREDENTIAL); + + mAuthContainer.onAttachedToWindowInternal(); + + verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null)); + verify(mAuthContainer.mBackgroundView).setImportantForAccessibility( + eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO)); + } + + @Test + public void testLayoutParams_hasSecureWindowFlag() { + final IBinder windowToken = mock(IBinder.class); + final WindowManager.LayoutParams layoutParams = + AuthContainerView.getLayoutParams(windowToken); + assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0); + } + private void initializeContainer(int authenticators) { AuthContainerView.Config config = new AuthContainerView.Config(); config.mContext = mContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index dcaf4ec6f8ae..d7f0f50d66db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -62,7 +62,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoveInterceptor; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -72,6 +71,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState 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.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index c9f5b40e8f9f..f40fc94763ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -39,10 +39,10 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleData.TimeSource; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.google.common.collect.ImmutableList; 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 897091f69f36..e3bcdc8b0b60 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 @@ -32,12 +32,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.DumpController import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController 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.assertTrue import org.junit.Before import org.junit.Test @@ -82,7 +83,7 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> private lateinit var delayableExecutor: FakeExecutor - private lateinit var controller: ControlsController + private lateinit var controller: ControlsControllerImpl companion object { fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() @@ -416,5 +417,70 @@ class ControlsControllerImplTest : SysuiTestCase() { verify(listingController).changeUser(UserHandle.of(otherUser)) assertTrue(controller.getFavoriteControls().isEmpty()) assertEquals(otherUser, controller.currentUserId) + assertTrue(controller.available) + } + + @Test + fun testDisableFeature_notAvailable() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, 0) + assertFalse(controller.available) + } + + @Test + fun testDisableFeature_clearFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertFalse(controller.getFavoriteControls().isEmpty()) + + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, user) + assertTrue(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testDisableFeature_noChangeForNotCurrentUser() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, otherUser) + + assertTrue(controller.available) + assertFalse(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testCorrectUserSettingOnUserChange() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, otherUser) + } + val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) + `when`(pendingResult.sendingUserId).thenReturn(otherUser) + broadcastReceiverCaptor.value.pendingResult = pendingResult + + broadcastReceiverCaptor.value.onReceive(mContext, intent) + + assertFalse(controller.available) + } + + @Test + fun testCountFavoritesForComponent_singleComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT_2)) + } + + @Test + fun testCountFavoritesForComponent_multipleComponents() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java deleted file mode 100644 index 4a90bb91ca37..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 com.android.systemui.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import junit.framework.Assert; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class RichEventTest extends SysuiTestCase { - - private static final int TOTAL_EVENT_TYPES = 1; - - @Test - public void testCreateRichEvent_invalidType() { - try { - // indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type - new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg"); - } catch (IllegalArgumentException e) { - // expected - return; - } - - Assert.fail("Expected an invalidArgumentException since the event type was invalid."); - } - - @Test - public void testCreateRichEvent() { - final int eventType = 0; - RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg"); - assertEquals(e.getType(), eventType); - } - - class TestableRichEvent extends RichEvent { - TestableRichEvent(int logLevel, int type, String reason) { - init(logLevel, type, reason); - } - - @Override - public String[] getEventLabels() { - return new String[]{"ACTION_NAME"}; - } - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java deleted file mode 100644 index e7b317e882ef..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 com.android.systemui.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.DumpController; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class SysuiLogTest extends SysuiTestCase { - private static final String TEST_ID = "TestLogger"; - private static final String TEST_MSG = "msg"; - private static final int MAX_LOGS = 5; - - @Mock - private DumpController mDumpController; - private SysuiLog<Event> mSysuiLog; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLogDisabled_noLogsWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false); - assertEquals(null, mSysuiLog.mTimeline); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(null, mSysuiLog.mTimeline); - } - - @Test - public void testLogEnabled_logWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(0, mSysuiLog.mTimeline.size()); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(1, mSysuiLog.mTimeline.size()); - } - - @Test - public void testMaxLogs() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(mSysuiLog.mTimeline.size(), 0); - - for (int i = 0; i < MAX_LOGS + 1; i++) { - mSysuiLog.log(createEvent(TEST_MSG + i)); - } - - assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size()); - - // check the first message (msg0) was replaced with msg1: - assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage()); - } - - @Test - public void testRecycleLogs() { - // GIVEN a SysuiLog with one log - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - Event e = createEvent(TEST_MSG); // msg - mSysuiLog.log(e); // Logs: [msg] - - Event recycledEvent = null; - // WHEN we add MAX_LOGS after the first log - for (int i = 0; i < MAX_LOGS; i++) { - recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i)); - } - // Logs: [msg1, msg2, msg3, msg4] - - // THEN we see the recycledEvent is e - assertEquals(e, recycledEvent); - } - - private Event createEvent(String msg) { - return new Event().init(msg); - } - - public class TestSysuiLog extends SysuiLog<Event> { - protected TestSysuiLog(DumpController dumpController, String id, int maxLogs, - boolean enabled) { - super(dumpController, id, maxLogs, enabled, false); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 0a3bc6def160..1d4b4be9e683 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitor.BatteryStatus; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 63c911b53db9..60163f26bb2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 4103edee6255..9d667a9a91c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -26,8 +26,8 @@ import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; 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 20c67fae6c9d..07f6936ece07 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 @@ -79,14 +79,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; 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.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; +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.stack.NotificationListContainer; @@ -99,6 +99,7 @@ import com.android.systemui.util.leak.LeakDetector; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -137,7 +138,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private RowInflaterTask mAsyncInflationTask; - @Mock private NotifLog mNotifLog; + @Mock private NotificationEntryManagerLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @Mock private LeakDetector mLeakDetector; @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; @@ -206,20 +207,20 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mRemoteInputManager); - contentBinder.setInflateSynchronously(true); - 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, mRemoteInputManager, mLockscreenUserManager, - contentBinder, + mock(NotifBindPipeline.class), + bindStage, true, /* allowLongPress */ mock(KeyguardBypassController.class), mock(StatusBarStateController.class), @@ -232,14 +233,14 @@ public class NotificationEntryManagerTest extends SysuiTestCase { when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( - mNotifLog, + mLogger, mGroupManager, new NotificationRankingManager( () -> mock(NotificationMediaManager.class), mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mNotifLog, + mLogger, mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class)), @@ -269,7 +270,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.abortTask(); } + // TODO: These tests are closer to functional tests and we should move them to their own file. + // and also strip some of the verifies that make the test too complex @Test + @Ignore public void testAddNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -306,6 +310,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -331,6 +336,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification_prePostEntryOrder() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -399,7 +405,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow).setEntry(eq(mEntry)); assertEquals(1, mEntry.getSmartActions().size()); assertEquals("action", mEntry.getSmartActions().get(0).title); verify(mEntryListener).onNotificationRankingUpdated(mRankingMap); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 5aed61b98ad9..1116a333125e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -42,11 +42,11 @@ import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index 7431459aac8f..0e730e5c3ffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -22,7 +22,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -34,7 +33,7 @@ import java.util.concurrent.CountDownLatch * Enable some test capabilities for NEM without making everything public on the base class */ class TestableNotificationEntryManager( - log: NotifLog, + logger: NotificationEntryManagerLogger, gm: NotificationGroupManager, rm: NotificationRankingManager, ke: KeyguardEnvironment, @@ -43,13 +42,13 @@ class TestableNotificationEntryManager( notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>, leakDetector: LeakDetector, fgsFeatureController: ForegroundServiceDismissalFeatureController -) : NotificationEntryManager(log, gm, rm, ke, ff, rb, +) : NotificationEntryManager(logger, gm, rm, ke, ff, rb, notificationRemoteInputManagerLazy, leakDetector, fgsFeatureController) { public var countDownLatch: CountDownLatch = CountDownLatch(1) - override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) { - super.onAsyncInflationFinished(entry, inflatedFlags) + override fun onAsyncInflationFinished(entry: NotificationEntry) { + super.onAsyncInflationFinished(entry) countDownLatch.countDown() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 7ab4846ea066..c6b496dd8215 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -27,10 +27,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT @@ -62,7 +62,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationGroupManager::class.java), mock(HeadsUpManager::class.java), mock(NotificationFilter::class.java), - mock(NotifLog::class.java), + mock(NotificationEntryManagerLogger::class.java), mock(NotificationSectionsFeatureManager::class.java), personNotificationIdentifier, HighPriorityProvider(personNotificationIdentifier) @@ -189,7 +189,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager: NotificationGroupManager, headsUpManager: HeadsUpManager, filter: NotificationFilter, - notifLog: NotifLog, + logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, peopleNotificationIdentifier: PeopleNotificationIdentifier, highPriorityProvider: HighPriorityProvider @@ -198,7 +198,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager, headsUpManager, filter, - notifLog, + logger, sectionsFeatureManager, peopleNotificationIdentifier, highPriorityProvider 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 3d79ce15bfb6..d8cf6ed9a47b 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 @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,7 +50,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; @@ -147,15 +145,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void setNeedsRedactionSetsInflationFlag() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - - row.setNeedsRedaction(true); - - assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC)); - } - - @Test public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); row.setNeedsRedaction(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java new file mode 100644 index 000000000000..8f9f65d12762 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -0,0 +1,158 @@ +/* + * 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 com.android.systemui.statusbar.notification.row; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.annotation.NonNull; +import androidx.core.os.CancellationSignal; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotifBindPipelineTest extends SysuiTestCase { + + private NotifBindPipeline mBindPipeline; + private TestBindStage mStage = new TestBindStage(); + + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + NotificationEntryListener entryListener = entryListenerCaptor.getValue(); + + entryListener.onPendingEntryAdded(mEntry); + } + + @Test + public void testCallbackCalled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN content is invalidated + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN stage finishes its work + mStage.doWorkSynchronously(); + + // THEN the callback is called when bind finishes + verify(callback).onBindFinished(mEntry); + } + + @Test + public void testCallbackCancelled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // GIVEN an in-progress pipeline run + BindCallback callback = mock(BindCallback.class); + CancellationSignal signal = mStage.requestRebind(mEntry, callback); + + // WHEN the callback is cancelled. + signal.cancel(); + + // WHEN the stage finishes all its work + mStage.doWorkSynchronously(); + + // THEN the callback is not called when bind finishes + verify(callback, never()).onBindFinished(mEntry); + } + + @Test + public void testMultipleCallbacks() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN the pipeline is invalidated. + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN the pipeline is invalidated again before the work completes. + BindCallback callback2 = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback2); + + // WHEN the stage finishes all work. + mStage.doWorkSynchronously(); + + // THEN both callbacks are called when the bind finishes + verify(callback).onBindFinished(mEntry); + verify(callback2).onBindFinished(mEntry); + } + + /** + * Bind stage for testing where asynchronous work can be synchronously controlled. + */ + private static class TestBindStage extends BindStage { + private List<Runnable> mExecutionRequests = new ArrayList<>(); + + @Override + protected void executeStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, @NonNull StageCallback callback) { + mExecutionRequests.add(() -> callback.onStageFinished(entry)); + } + + @Override + protected void abortStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + + } + + @Override + protected Object newStageParams() { + return null; + } + + public void doWorkSynchronously() { + for (Runnable work: mExecutionRequests) { + work.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java index d7214f3b9228..20cc01accbc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -32,10 +32,10 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { private NotifRemoteViewCacheImpl mNotifRemoteViewCache; private NotificationEntry mEntry; - private NotificationEntryListener mEntryListener; + private NotifCollectionListener mEntryListener; @Mock private RemoteViews mRemoteViews; @Before @@ -58,19 +58,17 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mEntry = new NotificationEntryBuilder().build(); - NotificationEntryManager entryManager = mock(NotificationEntryManager.class); - mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); - ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = - ArgumentCaptor.forClass(NotificationEntryListener.class); - verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + CommonNotifCollection collection = mock(CommonNotifCollection.class); + mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(collection); + ArgumentCaptor<NotifCollectionListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotifCollectionListener.class); + verify(collection).addCollectionListener(entryListenerCaptor.capture()); mEntryListener = entryListenerCaptor.getValue(); + mEntryListener.onEntryInit(mEntry); } @Test public void testPutCachedView() { - // GIVEN an initialized cache for an entry. - mEntryListener.onPendingEntryAdded(mEntry); - // WHEN a notification's cached remote views is put in. mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); @@ -85,7 +83,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testRemoveCachedView() { // GIVEN a cache with a cached view. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); // WHEN we remove the cached view. @@ -98,7 +95,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testClearCache() { // GIVEN a non-empty cache. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 444a6e5b4b13..1dfe7bc33373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cb9da6a40cb9..8a42e5fb4ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -49,9 +49,7 @@ import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; @@ -200,8 +198,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { countDownLatch.countDown(); } }, mRow.getPrivateLayout(), null, null, new HashMap<>(), @@ -219,34 +216,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase { assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } - /* Cancelling requires us to be on the UI thread otherwise we might have a race */ - @Test - public void testSupersedesExistingTask() { - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_ALL, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - // Trigger inflation of contracted only. - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_CONTRACTED, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - InflationTask runningTask = mRow.getEntry().getRunningTask(); - NotificationContentInflater.AsyncInflationTask asyncInflationTask = - (NotificationContentInflater.AsyncInflationTask) runningTask; - assertEquals("Successive inflations don't inherit the previous flags!", - FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags()); - runningTask.abort(); - } - @Test public void doesntReapplyDisallowedRemoteView() throws Exception { mBuilder.setStyle(new Notification.MediaStyle()); @@ -349,8 +318,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (expectingException) { exceptionHolder.setException(new RuntimeException( "Inflation finished even though there should be an error")); 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 20a089f97f51..f080d67bfffb 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 @@ -655,13 +655,13 @@ public class NotificationConversationInfoTest extends SysuiTestCase { ArgumentCaptor.forClass(NotificationChannel.class); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().canBypassDnd()); + assertTrue(captor.getValue().isImportantConversation()); } @Test public void testFavorite_unfavorite() throws Exception { - mNotificationChannel.setBypassDnd(true); - mConversationChannel.setBypassDnd(true); + mNotificationChannel.setImportantConversation(true); + mConversationChannel.setImportantConversation(true); mNotificationInfo.bindNotification( mShortcutManager, @@ -688,7 +688,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { ArgumentCaptor.forClass(NotificationChannel.class); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), anyInt(), captor.capture()); - assertFalse(captor.getValue().canBypassDnd()); + assertFalse(captor.getValue().isImportantConversation()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 4e27770982e5..bbb6723135a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -66,7 +66,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 457bbe23334b..3d9832de417a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.systemui.statusbar.notification.row; import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.app.ActivityManager; @@ -40,17 +41,20 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -58,6 +62,8 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.tests.R; +import org.mockito.ArgumentCaptor; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -82,6 +88,9 @@ public class NotificationTestHelper { private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; private HeadsUpManagerPhone mHeadsUpManager; + private final NotifBindPipeline mBindPipeline; + private final NotificationEntryListener mBindPipelineEntryListener; + private final RowContentBindStage mBindStage; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -95,6 +104,23 @@ public class NotificationTestHelper { mock(KeyguardBypassController.class)); mHeadsUpManager.setUp(null, mGroupManager, null, null); mGroupManager.setHeadsUpManager(mHeadsUpManager); + + + NotificationContentInflater contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mock(NotificationRemoteInputManager.class)); + contentBinder.setInflateSynchronously(true); + mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class)); + + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mBindStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + mBindPipelineEntryListener = entryListenerCaptor.getValue(); } /** @@ -331,10 +357,8 @@ public class NotificationTestHelper { entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mock(NotificationRemoteInputManager.class)); - contentBinder.setInflateSynchronously(true); + mBindPipelineEntryListener.onPendingEntryAdded(entry); + mBindPipeline.manageRow(entry, row); row.initialize( APP_NAME, @@ -343,12 +367,11 @@ public class NotificationTestHelper { mock(KeyguardBypassController.class), mGroupManager, mHeadsUpManager, - contentBinder, + mBindStage, mock(OnExpandClickListener.class)); row.setAboveShelfChangedListener(aboveShelf -> { }); - - row.setInflationFlags(extraInflationFlags); - inflateAndWait(row); + mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); + inflateAndWait(entry, mBindStage); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of // the callback chain, so we need to make up for not adding it to the group manager @@ -357,24 +380,10 @@ public class NotificationTestHelper { return row; } - private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { + private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage) + throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - NotificationContentInflater.InflationCallback callback = - new NotificationContentInflater.InflationCallback() { - @Override - public void handleInflationException(NotificationEntry entry, - Exception e) { - countDownLatch.countDown(); - } - - @Override - public void onAsyncInflationFinished(NotificationEntry entry, - int inflatedFlags) { - countDownLatch.countDown(); - } - }; - row.setInflationCallback(callback); - row.inflateViews(); + stage.requestRebind(entry, en -> countDownLatch.countDown()); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java new file mode 100644 index 000000000000..775f722b13f9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -0,0 +1,269 @@ +/* + * 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.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class RowContentBindStageTest extends SysuiTestCase { + + private RowContentBindStage mRowContentBindStage; + + @Mock private NotificationRowContentBinder mBinder; + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mRowContentBindStage = new RowContentBindStage(mBinder, + mock(IStatusBarService.class)); + mRowContentBindStage.createStageParams(mEntry); + } + + @Test + public void testRequireContentViews() { + // WHEN inflation flags are set and pipeline is invalidated. + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test + public void testFreeContentViews() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + + // WHEN inflation flags are cleared and stage executed. + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.freeContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder unbinds flags. + verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags)); + } + + @Test + public void testRebindAllContentViews() { + // GIVEN a view with content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(flags); + params.clearDirtyContentViews(); + + // WHEN we request rebind and stage executed. + params.rebindAllContentViews(); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test + public void testSetUseLowPriority() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN low priority is set and stage executed. + params.setUseLowPriority(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use low priority and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isLowPriority); + } + + @Test + public void testSetUseGroupInChild() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use group is set and stage executed. + params.setUseChildInGroup(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isChildInGroup); + } + + @Test + public void testSetUseIncreasedHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased height is set and stage executed. + params.setUseIncreasedCollapsedHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with group view and contracted is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeight); + } + + @Test + public void testSetUseIncreasedHeadsUpHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased heads up height is set and stage executed. + params.setUseIncreasedHeadsUpHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and heads up is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_HEADS_UP), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeadsUpHeight); + } + + @Test + public void testSetNeedsReinflation() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN needs reinflation is set. + params.setNeedsReinflation(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with forceInflate and all views are requested to bind. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_ALL), + any(), + eq(true), + any()); + } + + @Test + public void testSupersedesPreviousContentViews() { + // GIVEN a view with content view bind already in progress. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + int defaultFlags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(defaultFlags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // WHEN we bind with another content view before the first finishes. + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with BOTH content views. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags), + any(), + anyBoolean(), + any()); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags | FLAG_CONTENT_VIEW_HEADS_UP), + any(), + anyBoolean(), + any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index d280f185edd3..0790cb7ca6c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -25,8 +25,8 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.tests.R; import org.junit.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java index 4f45f680f475..038eff7fa5dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java @@ -38,8 +38,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 14e2fded6cdc..9567f3386dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -29,8 +29,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index ddd2884ec311..1773175450ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -25,8 +25,8 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; 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 34a309f1d80c..e84f14a6a2c1 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 @@ -31,11 +31,11 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.util.DeviceConfigProxy; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 70d76f0c3a52..b16e52ce7bd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.TestableNotificationEntryManager; @@ -74,7 +75,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.FooterView; @@ -163,14 +163,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor .forClass(UserChangedListener.class); mEntryManager = new TestableNotificationEntryManager( - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationGroupManager.class), new NotificationRankingManager( () -> mock(NotificationMediaManager.class), mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 7448dbd0d116..f71d0fc4b43e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -35,9 +35,9 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index 5b54fba5b3b5..e171a2894d2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -38,6 +42,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; @@ -47,6 +54,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -62,8 +70,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { private NotificationGroupManager mGroupManager; private HeadsUpManager mHeadsUpManager; @Mock private NotificationEntryManager mNotificationEntryManager; - @Captor - private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; + @Mock private RowContentBindStage mBindStage; + @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; private NotificationEntryListener mNotificationEntryListener; private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = @@ -72,6 +80,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Before public void setup() { + MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(BubbleController.class); mHeadsUpManager = new HeadsUpManager(mContext) {}; @@ -82,7 +91,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(); + when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); + + mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); @@ -97,6 +108,10 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); + RowContentBindParams params = new RowContentBindParams(); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + // Summary will be suppressed because there is only one child. mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -160,8 +175,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -178,15 +193,16 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + // Child entry finishes its inflation. + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey())); @@ -199,8 +215,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); mHeadsUpManager.showNotification(summaryEntry); @@ -214,9 +231,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -229,8 +246,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -247,8 +265,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -270,8 +289,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index 54dc728e0c8b..d405fea78170 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -87,7 +86,6 @@ public final class NotificationGroupTestHelper { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); entry.setRow(row); when(row.getEntry()).thenReturn(entry); - when(row.isInflationFlagSet(anyInt())).thenReturn(true); return entry; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index fea4b8bbbb04..50276106f8d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -61,7 +61,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; @@ -70,6 +69,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 390e812b3613..df622542e22c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -39,9 +39,9 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt new file mode 100644 index 000000000000..8eecde1f4f7c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -0,0 +1,218 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FloatingContentCoordinatorTest : SysuiTestCase() { + + private val screenBounds = Rect(0, 0, 1000, 1000) + + private val rect100px = Rect() + private val rect100pxFloating = FloatingRect(rect100px) + + private val rect200px = Rect() + private val rect200pxFloating = FloatingRect(rect200px) + + private val rect300px = Rect() + private val rect300pxFloating = FloatingRect(rect300px) + + private val floatingCoordinator = FloatingContentCoordinator() + + @Before + fun setup() { + rect100px.set(0, 0, 100, 100) + rect200px.set(0, 0, 200, 200) + rect300px.set(0, 0, 300, 300) + } + + @After + fun tearDown() { + // We need to remove this stuff since it's a singleton object and it'll be there for the + // next test. + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect200pxFloating) + floatingCoordinator.onContentRemoved(rect300pxFloating) + } + + @Test + fun testOnContentAdded() { + // Add rect1, and verify that the coordinator didn't move it. + floatingCoordinator.onContentAdded(rect100pxFloating) + assertEquals(rect100px.top, 0) + + // Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added + // content is allowed to remain where it is. rect1 should have been moved below rect2 + // since it was in the way. + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + } + + @Test + fun testOnContentRemoved() { + // Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should + // no longer be considered in the way, so it shouldn't move when rect2 is added. + floatingCoordinator.onContentAdded(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect100px.top, 0) + assertEquals(rect200px.top, 0) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_twoRects() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 0px where it will absolutely conflict with rect1. + rect200px.offsetTo(0, 0) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now + // be at y = 200. + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + + // Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push + // rect1 upward and leave rect2 alone. + rect200px.offsetTo(0, 275) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 275) + assertEquals(rect100px.top, 175) + + verifyRectSizes() + + // Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of + // mass. That means rect1 should be pushed downward. + rect200px.offsetTo(0, 110) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 110) + assertEquals(rect100px.top, 310) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_threeRects() { + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Add rect2, which should displace rect1 to y = 200 + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + // Add rect3, which should completely cover both rect1 and rect2. That should cause them to + // move away. The order in which they do so is non-deterministic, so just make sure none of + // the three Rects intersect. + floatingCoordinator.onContentAdded(rect300pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + + // Move rect2 to intersect both rect1 and rect3. + rect200px.offsetTo(0, 150) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + } + + @Test + fun testOnContentMoved_respectsUpperBounds() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 90px where it will conflict with rect1, but with a center of mass below + // that of rect1's. This would normally mean that rect1 moves upward. However, since it's at + // the top of the screen, it should go downward instead. + rect200px.offsetTo(0, 90) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it + // was intersected from below. + assertEquals(rect200px.top, 90) + assertEquals(rect100px.top, 290) + } + + @Test + fun testOnContentMoved_respectsLowerBounds() { + // Put rect1 at the bottom of the screen and add it. + rect100px.offsetTo(0, screenBounds.bottom - 100) + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would + // normally move downward. Since it's at the bottom of the screen, it should go upward + // instead. + rect200px.offsetTo(0, 800) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect200px.top, 800) + assertEquals(rect100px.top, 700) + } + + /** + * Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us + * to assert only the value of rect.top in tests, since if top, width, and height are correct, + * that means top/left/right/bottom are all correct. + */ + private fun verifyRectSizes() { + assertEquals(100, rect100px.width()) + assertEquals(200, rect200px.width()) + assertEquals(300, rect300px.width()) + + assertEquals(100, rect100px.height()) + assertEquals(200, rect200px.height()) + assertEquals(300, rect300px.height()) + } + + /** + * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a + * Rect when needed. + */ + inner class FloatingRect( + private val underlyingRect: Rect + ) : FloatingContentCoordinator.FloatingContent { + override fun moveToBounds(bounds: Rect) { + underlyingRect.set(bounds) + } + + override fun getAllowedFloatingBoundsRegion(): Rect { + return screenBounds + } + + override fun getFloatingBoundsOnScreen(): Rect { + return underlyingRect + } + } +}
\ No newline at end of file diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 07abe1adeb52..39c402be84a3 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -272,13 +272,6 @@ public class Tethering { mStateReceiver = new StateReceiver(); - mNetdCallback = new NetdCallback(); - try { - mNetd.registerUnsolicitedEventListener(mNetdCallback); - } catch (RemoteException e) { - mLog.e("Unable to register netd UnsolicitedEventListener"); - } - final UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); @@ -287,6 +280,14 @@ public class Tethering { // Load tethering configuration. updateConfiguration(); + // NetdCallback should be registered after updateConfiguration() to ensure + // TetheringConfiguration is created. + mNetdCallback = new NetdCallback(); + try { + mNetd.registerUnsolicitedEventListener(mNetdCallback); + } catch (RemoteException e) { + mLog.e("Unable to register netd UnsolicitedEventListener"); + } startStateMachineUpdaters(mHandler); startTrackDefaultNetwork(); diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index fc7709c4a51e..dcdb80b497d0 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -28,6 +28,7 @@ LOCAL_REQUIRED_MODULES := \ DisplayCutoutEmulationDoubleOverlay \ DisplayCutoutEmulationHoleOverlay \ DisplayCutoutEmulationTallOverlay \ + DisplayCutoutEmulationWaterfallOverlay \ FontNotoSerifSourceOverlay \ IconPackCircularAndroidOverlay \ IconPackCircularLauncherOverlay \ diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk new file mode 100644 index 000000000000..b6b6dd1c25bc --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := DisplayCutoutEmulationWaterfall + + +LOCAL_PRODUCT_MODULE := true + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWaterfallOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..2d5bb14f4dd3 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.display.cutout.emulation.waterfall" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:category="com.android.internal.display_cutout_emulation" + android:priority="1"/> + + <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml new file mode 100644 index 000000000000..df2f3d19626e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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. + --> + +<resources> + <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape --> + <dimen name="quick_qs_offset_height">48dp</dimen> + <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 --> + <dimen name="quick_qs_total_height">176dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml new file mode 100644 index 000000000000..6f692c8021c0 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml @@ -0,0 +1,35 @@ +<!-- + ~ 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. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Height of the status bar in portrait. The height should be + Max((status bar content height + waterfall top size), top cutout size) --> + <dimen name="status_bar_height_portrait">28dp</dimen> + <!-- Max((28 + 20), 0) = 48 --> + <dimen name="status_bar_height_landscape">48dp</dimen> + <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> + <dimen name="quick_qs_offset_height">28dp</dimen> + <!-- Total height of QQS (quick_qs_offset_height + 128) --> + <dimen name="quick_qs_total_height">156dp</dimen> + + <dimen name="waterfall_display_left_edge_size">20dp</dimen> + <dimen name="waterfall_display_top_edge_size">0dp</dimen> + <dimen name="waterfall_display_right_edge_size">20dp</dimen> + <dimen name="waterfall_display_bottom_edge_size">0dp</dimen> +</resources> + + diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml new file mode 100644 index 000000000000..ed073d0e244e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml @@ -0,0 +1,21 @@ +<?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. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <string name="display_cutout_emulation_overlay">Waterfall cutout</string> + +</resources>
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index a5877ccbde7c..565ee63d89ab 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER; import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; @@ -36,10 +38,11 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; @@ -50,6 +53,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -71,6 +75,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.wm.ActivityTaskManagerInternal; @@ -106,6 +111,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private final PowerManager mPowerManager; private final IPlatformCompat mIPlatformCompat; + private final Handler mMainHandler; + // Handler for scheduling method invocations on the main thread. public final InvocationHandler mInvocationHandler; @@ -238,6 +245,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSecurityPolicy = securityPolicy; mSystemActionPerformer = systemActionPerfomer; mSystemSupport = systemSupport; + mMainHandler = mainHandler; mInvocationHandler = new InvocationHandler(mainHandler.getLooper()); mA11yWindowManager = a11yWindowManager; mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); @@ -959,52 +967,72 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } - @Nullable @Override - public Bitmap takeScreenshot(int displayId) { + public void takeScreenshot(int displayId, RemoteCallback callback) { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { - return null; + sendScreenshotResult(true, null, callback); + return; } if (!mSecurityPolicy.canTakeScreenshotLocked(this)) { - return null; + sendScreenshotResult(true, null, callback); + throw new SecurityException("Services don't have the capability of taking" + + " the screenshot."); } } if (!mSecurityPolicy.checkAccessibilityAccess(this)) { - return null; + sendScreenshotResult(true, null, callback); + return; } final Display display = DisplayManagerGlobal.getInstance() .getRealDisplay(displayId); if (display == null) { - return null; + sendScreenshotResult(true, null, callback); + return; } - final Point displaySize = new Point(); - display.getRealSize(displaySize); - final int rotation = display.getRotation(); - Bitmap screenShot = null; + sendScreenshotResult(false, display, callback); + } + private void sendScreenshotResult(boolean noResult, Display display, RemoteCallback callback) { + final boolean noScreenshot = noResult; final long identity = Binder.clearCallingIdentity(); try { - final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - // TODO (b/145893483): calling new API with the display as a parameter - // when surface control supported. - screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y, - rotation); - if (screenShot != null) { - // Optimization for telling the bitmap that all of the pixels are known to be - // opaque (false). This is meant as a drawing hint, as in some cases a bitmap - // that is known to be opaque can take a faster drawing case than one that may - // have non-opaque per-pixel alpha values. - screenShot.setHasAlpha(false); - } + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { + if (noScreenshot) { + callback.sendResult(null); + return; + } + final Point displaySize = new Point(); + // TODO (b/145893483): calling new API with the display as a parameter + // when surface control supported. + final IBinder token = SurfaceControl.getInternalDisplayToken(); + final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); + final int rotation = display.getRotation(); + display.getRealSize(displaySize); + + final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = + SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, crop, + displaySize.x, displaySize.y, false, + rotation); + final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); + final HardwareBuffer hardwareBuffer = + HardwareBuffer.createFromGraphicBuffer(graphicBuffer); + final int colorSpaceId = screenshotBuffer.getColorSpace().getId(); + + // Send back the result. + final Bundle payload = new Bundle(); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, + hardwareBuffer); + payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId); + callback.sendResult(payload); + }, null).recycleOnUse()); } finally { Binder.restoreCallingIdentity(identity); } - return screenShot; } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 25911a7ed0ea..edb4445151d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -16,8 +16,6 @@ package com.android.server.accessibility; -import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT; - import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -27,20 +25,16 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Process; -import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.view.Display; -import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -393,15 +387,4 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } } - - @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) { - mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final Bitmap screenshot = super.takeScreenshot(displayId); - // Send back the result. - final Bundle payload = new Bundle(); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot); - callback.sendResult(payload); - }, null).recycleOnUse()); - } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 5d9af26a8339..d1c3a02c6761 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -328,6 +328,6 @@ class UiAutomationManager { public void onFingerprintGesture(int gesture) {} @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} + public void takeScreenshot(int displayId, RemoteCallback callback) {} } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 5d170d34d77d..b74be7e3f345 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -30,6 +30,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN; @@ -133,6 +140,13 @@ class GestureManifold implements GestureMatcher.StateChangeListener { new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); + // Four-finger taps. + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 1, GESTURE_4_FINGER_SINGLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this)); // Two-finger swipes. mMultiFingerGestures.add( new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this)); @@ -151,6 +165,15 @@ class GestureManifold implements GestureMatcher.StateChangeListener { new MultiFingerSwipe(context, 3, RIGHT, GESTURE_3_FINGER_SWIPE_RIGHT, this)); mMultiFingerGestures.add( new MultiFingerSwipe(context, 3, UP, GESTURE_3_FINGER_SWIPE_UP, this)); + // Four-finger swipes. + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, DOWN, GESTURE_4_FINGER_SWIPE_DOWN, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, LEFT, GESTURE_4_FINGER_SWIPE_LEFT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, RIGHT, GESTURE_4_FINGER_SWIPE_RIGHT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, UP, GESTURE_4_FINGER_SWIPE_UP, this)); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java index 8249239e3602..a14584a63bb2 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java @@ -139,7 +139,7 @@ class MultiFingerSwipe extends GestureMatcher { final int actionIndex = getActionIndex(rawEvent); final int pointerId = rawEvent.getPointerId(actionIndex); int pointerIndex = rawEvent.getPointerCount() - 1; - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -185,7 +185,7 @@ class MultiFingerSwipe extends GestureMatcher { } final int actionIndex = getActionIndex(rawEvent); final int pointerId = rawEvent.getPointerId(actionIndex); - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -224,7 +224,7 @@ class MultiFingerSwipe extends GestureMatcher { mCurrentFingerCount -= 1; final int actionIndex = getActionIndex(event); final int pointerId = event.getPointerId(actionIndex); - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -250,11 +250,29 @@ class MultiFingerSwipe extends GestureMatcher { @Override protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - for (int pointerIndex = 0; pointerIndex < rawEvent.getPointerCount(); ++pointerIndex) { + for (int pointerIndex = 0; pointerIndex < mTargetFingerCount; ++pointerIndex) { + if (mPointerIds[pointerIndex] == INVALID_POINTER_ID) { + // Fingers have started to move before the required number of fingers are down. + // However, they can still move less than the touch slop and still be considered + // touching, not moving. + // So we just ignore fingers that haven't been assigned a pointer id and process + // those who have. + continue; + } if (DEBUG) { Slog.d(getGestureName(), "Processing move on finger " + pointerIndex); } int index = rawEvent.findPointerIndex(mPointerIds[pointerIndex]); + if (index < 0) { + // This finger is not present in this event. It could have gone up just before this + // movement. + if (DEBUG) { + Slog.d( + getGestureName(), + "Finger " + pointerIndex + " not found in this event. skipping."); + } + continue; + } final float x = rawEvent.getX(index); final float y = rawEvent.getY(index); if (x < 0f || y < 0f) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 1a4fc32a76cd..1cb9313d9bf9 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -818,27 +818,26 @@ final class AutofillManagerServiceImpl } } - void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, - @Nullable Bundle clientState) { + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) { synchronized (mLock) { if (mAugmentedAutofillEventHistory == null || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { return; } mAugmentedAutofillEventHistory.addEvent( - new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null, + new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null, null, null, null, null, null, null)); } } - void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) { + void logAugmentedAutofillShown(int sessionId) { synchronized (mLock) { if (mAugmentedAutofillEventHistory == null || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { return; } mAugmentedAutofillEventHistory.addEvent( - new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, + new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null, null, null, null, null, null)); } @@ -1227,16 +1226,15 @@ final class AutofillManagerServiceImpl } @Override - public void logAugmentedAutofillShown(int sessionId, Bundle clientState) { - AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId, - clientState); + public void logAugmentedAutofillShown(int sessionId) { + AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId); } @Override - public void logAugmentedAutofillSelected(int sessionId, String suggestionId, - Bundle clientState) { + public void logAugmentedAutofillSelected(int sessionId, + String suggestionId) { AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId, - suggestionId, clientState); + suggestionId); } @Override diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 5e6f6fea4dda..880c40158114 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -55,6 +55,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.IResultReceiver; import com.android.internal.util.ArrayUtils; import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.server.autofill.ui.InlineSuggestionFactory; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; @@ -144,7 +145,8 @@ final class RemoteAugmentedAutofillService int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, - @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) { + @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, + @NonNull Runnable onErrorCallback) { long requestTime = SystemClock.elapsedRealtime(); AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>(); @@ -161,12 +163,12 @@ final class RemoteAugmentedAutofillService focusedId, focusedValue, requestTime, inlineSuggestionsRequest, new IFillCallback.Stub() { @Override - public void onSuccess(@Nullable Dataset[] inlineSuggestionsData, - @Nullable Bundle clientState) { + public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, inlineSuggestionsData, focusedId, - inlineSuggestionsCallback, client, clientState); + inlineSuggestionsCallback, client, + onErrorCallback); requestAutofill.complete(null); } @@ -231,29 +233,31 @@ final class RemoteAugmentedAutofillService private void maybeRequestShowInlineSuggestions(int sessionId, @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId, @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, - @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) { + @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) { if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) { return; } mCallbacks.setLastResponse(sessionId); + try { inlineSuggestionsCallback.onInlineSuggestionsResponse( InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse( inlineSuggestionsData, focusedId, mContext, dataset -> { mCallbacks.logAugmentedAutofillSelected(sessionId, - dataset.getId(), clientState); + dataset.getId()); try { client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues()); } catch (RemoteException e) { Slog.w(TAG, "Encounter exception autofilling the values"); } - })); + }, onErrorCallback)); } catch (RemoteException e) { Slog.w(TAG, "Exception sending inline suggestions response back to IME."); } - mCallbacks.logAugmentedAutofillShown(sessionId, clientState); + + mCallbacks.logAugmentedAutofillShown(sessionId); } @Override @@ -275,9 +279,8 @@ final class RemoteAugmentedAutofillService void setLastResponse(int sessionId); - void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState); + void logAugmentedAutofillShown(int sessionId); - void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, - @Nullable Bundle clientState); + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 415ecd8cfd63..7e5123c82054 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -103,6 +103,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.server.autofill.ui.AutoFillUI; +import com.android.server.autofill.ui.InlineSuggestionFactory; import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -2681,7 +2682,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, serviceLabel, serviceIcon, this, id, mCompatMode); @@ -2733,7 +2733,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState InlineSuggestionsResponse inlineSuggestionsResponse = InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(), - datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this); + datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this, () -> { + synchronized (mLock) { + requestHideFillUi(mCurrentViewId); + } + }); try { inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse); } catch (RemoteException e) { @@ -3024,7 +3028,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mInlineSuggestionsRequestCallback != null ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, - currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback); + currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> { + synchronized (mLock) { + cancelAugmentedAutofillLocked(); + } + }); if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index cb6c8f5d3ea5..38a5b5b1cdaa 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.autofill; +package com.android.server.autofill.ui; import static com.android.server.autofill.Helper.sDebug; @@ -32,18 +32,13 @@ import android.view.inputmethod.InlineSuggestion; import android.view.inputmethod.InlineSuggestionInfo; import android.view.inputmethod.InlineSuggestionsResponse; +import com.android.internal.util.function.QuadFunction; import com.android.internal.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; import com.android.server.UiThread; -import com.android.server.autofill.ui.AutoFillUI; -import com.android.server.autofill.ui.InlineSuggestionUi; import java.util.ArrayList; - -/** - * @hide - */ public final class InlineSuggestionFactory { private static final String TAG = "InlineSuggestionFactory"; @@ -65,28 +60,12 @@ public final class InlineSuggestionFactory { @NonNull Dataset[] datasets, @NonNull AutofillId autofillId, @NonNull Context context, - @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) { - if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); - - final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); - final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context); - for (Dataset dataset : datasets) { - final int fieldIndex = dataset.getFieldIds().indexOf(autofillId); - if (fieldIndex < 0) { - Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset"); - return null; - } - final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation( - fieldIndex); - if (inlinePresentation == null) { - Slog.w(TAG, "InlinePresentation not found in dataset"); - return null; - } - InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset, - inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback); - inlineSuggestions.add(inlineSuggestion); - } - return new InlineSuggestionsResponse(inlineSuggestions); + @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback, + @NonNull Runnable onErrorCallback) { + return createInlineSuggestionsResponseInternal(datasets, autofillId, + context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi, + filedIndex) -> createAugmentedInlineSuggestion(dataset, + inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback)); } /** @@ -97,11 +76,26 @@ public final class InlineSuggestionFactory { @NonNull Dataset[] datasets, @NonNull AutofillId autofillId, @NonNull Context context, - @NonNull AutoFillUI.AutoFillUiCallback client) { - if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called"); + @NonNull AutoFillUI.AutoFillUiCallback client, + @NonNull Runnable onErrorCallback) { + return createInlineSuggestionsResponseInternal(datasets, autofillId, + context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi, + filedIndex) -> createInlineSuggestion(requestId, dataset, filedIndex, + inlinePresentation, inlineSuggestionUi, client)); + } + + private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal( + @NonNull Dataset[] datasets, + @NonNull AutofillId autofillId, + @NonNull Context context, + @NonNull Runnable onErrorCallback, + @NonNull QuadFunction<Dataset, InlinePresentation, InlineSuggestionUi, + Integer, InlineSuggestion> suggestionFactory) { + if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); - final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context); + final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context, + onErrorCallback); for (Dataset dataset : datasets) { final int fieldIndex = dataset.getFieldIds().indexOf(autofillId); if (fieldIndex < 0) { @@ -114,9 +108,8 @@ public final class InlineSuggestionFactory { Slog.w(TAG, "InlinePresentation not found in dataset"); return null; } - InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset, - fieldIndex, - inlinePresentation, inlineSuggestionUi, client); + InlineSuggestion inlineSuggestion = suggestionFactory.apply(dataset, + inlinePresentation, inlineSuggestionUi, fieldIndex); inlineSuggestions.add(inlineSuggestion); } return new InlineSuggestionsResponse(inlineSuggestions); @@ -131,9 +124,8 @@ public final class InlineSuggestionFactory { inlinePresentation.getInlinePresentationSpec(), InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""}, InlineSuggestionInfo.TYPE_SUGGESTION); - final View.OnClickListener onClickListener = v -> { + final View.OnClickListener onClickListener = v -> inlineSuggestionUiCallback.autofill(dataset); - }; final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlinePresentation, inlineSuggestionUi, onClickListener)); diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java new file mode 100644 index 000000000000..8d476d72c639 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java @@ -0,0 +1,82 @@ +/* + * 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.autofill.ui; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.util.MathUtils; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.widget.FrameLayout; + +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; + +/** + * This class is the root view for an inline suggestion. It is responsible for + * detecting the click on the item and to also transfer input focus to the IME + * window if we detect the user is scrolling. + */ + // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus +@SuppressLint("ViewConstructor") +class InlineSuggestionRoot extends FrameLayout { + private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName(); + + private final @NonNull Runnable mOnErrorCallback; + private final int mTouchSlop; + + private float mDownX; + private float mDownY; + + InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) { + super(context); + mOnErrorCallback = onErrorCallback; + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(@NonNull MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDownX = event.getX(); + mDownY = event.getY(); + } break; + + case MotionEvent.ACTION_MOVE: { + final float distance = MathUtils.dist(mDownX, mDownY, + event.getX(), event.getY()); + if (distance > mTouchSlop) { + transferTouchFocusToImeWindow(); + } + } break; + } + return super.onTouchEvent(event); + } + + private void transferTouchFocusToImeWindow() { + final WindowManagerInternal windowManagerInternal = LocalServices.getService( + WindowManagerInternal.class); + if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(), + getContext().getDisplayId())) { + Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME"); + mOnErrorCallback.run(); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java index 2adefeabfcc8..bf148a642bbd 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java @@ -67,10 +67,12 @@ public class InlineSuggestionUi { // (int)}. This name is a single string of the form "package:type/entry". private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)"); - private final Context mContext; + private final @NonNull Context mContext; + private final @NonNull Runnable mOnErrorCallback; - public InlineSuggestionUi(Context context) { + InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) { this.mContext = context; + mOnErrorCallback = onErrorCallback; } /** @@ -94,15 +96,17 @@ public class InlineSuggestionUi { } final View suggestionView = renderSlice(inlinePresentation.getSlice(), contextThemeWrapper); - if (onClickListener != null) { - suggestionView.setOnClickListener(onClickListener); - } + + final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot( + mContext, mOnErrorCallback); + suggestionRoot.addView(suggestionView); + suggestionRoot.setOnClickListener(onClickListener); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); - wvr.addView(suggestionView, lp); + wvr.addView(suggestionRoot, lp); return sc; } diff --git a/services/core/Android.bp b/services/core/Android.bp index a603fa975b74..f33237f490e4 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -115,8 +115,8 @@ java_library_static { "android.hardware.health-V2.0-java", "android.hardware.light-java", "android.hardware.weaver-V1.0-java", - "android.hardware.biometrics.face-V1.0-java", - "android.hardware.biometrics.fingerprint-V2.1-java", + "android.hardware.biometrics.face-V1.1-java", + "android.hardware.biometrics.fingerprint-V2.2-java", "android.hardware.oemlock-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ce5e241e5b88..caacf13d7bc4 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -48,8 +48,11 @@ import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; +import static java.util.Map.Entry; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.NotificationManager; import android.app.PendingIntent; @@ -62,6 +65,8 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.net.CaptivePortal; import android.net.ConnectionInfo; +import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; import android.net.ICaptivePortal; import android.net.IConnectivityDiagnosticsCallback; @@ -130,6 +135,7 @@ import android.os.Message; import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -170,6 +176,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; @@ -492,9 +499,9 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has * been tested. - * obj = String representing URL that Internet probe was redirect to, if it was redirected. - * arg1 = One of the NETWORK_TESTED_RESULT_* constants. - * arg2 = NetID. + * obj = {@link NetworkTestedResults} representing information sent from NetworkMonitor. + * data = PersistableBundle of extras passed from NetworkMonitor. If {@link + * NetworkMonitorCallbacks#notifyNetworkTested} is called, this will be null. */ private static final int EVENT_NETWORK_TESTED = 41; @@ -595,7 +602,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private Set<String> mWolSupportedInterfaces; - private TelephonyManager mTelephonyManager; + private final TelephonyManager mTelephonyManager; + private final AppOpsManager mAppOpsManager; + + private final LocationPermissionChecker mLocationPermissionChecker; private KeepaliveTracker mKeepaliveTracker; private NetworkNotificationManager mNotifier; @@ -951,6 +961,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDeps = Objects.requireNonNull(deps, "missing Dependencies"); mSystemProperties = mDeps.getSystemProperties(); mNetIdManager = mDeps.makeNetIdManager(); + mContext = Objects.requireNonNull(context, "missing Context"); mMetricsLog = logger; mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); @@ -979,7 +990,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); - mContext = Objects.requireNonNull(context, "missing Context"); mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); mPolicyManager = Objects.requireNonNull(policyManager, "missing INetworkPolicyManager"); @@ -992,6 +1002,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetd = netd; mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mLocationPermissionChecker = new LocationPermissionChecker(mContext); // To ensure uid rules are synchronized with Network Policy, register for // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService @@ -1157,6 +1169,7 @@ public class ConnectivityService extends IConnectivityManager.Stub int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); if (transportType > -1) { netCap.addTransportType(transportType); } @@ -1687,10 +1700,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return newLp; } - private void restrictRequestUidsForCaller(NetworkCapabilities nc) { + private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc, + int callerUid, String callerPackageName) { if (!checkSettingsPermission()) { - nc.setSingleUid(Binder.getCallingUid()); + nc.setSingleUid(callerUid); } + nc.setRequestorUidAndPackageName(callerUid, callerPackageName); nc.setAdministratorUids(Collections.EMPTY_LIST); // Clear owner UID; this can never come from an app. @@ -2101,6 +2116,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private boolean checkNetworkStackPermission(int pid, int uid) { + return checkAnyPermissionOf(pid, uid, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) { return checkAnyPermissionOf(pid, uid, android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP, @@ -2747,88 +2768,21 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_NETWORK_TESTED: { - final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); - if (nai == null) break; + final NetworkTestedResults results = (NetworkTestedResults) msg.obj; - final boolean wasPartial = nai.partialConnectivity; - nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); - final boolean partialConnectivityChanged = - (wasPartial != nai.partialConnectivity); - - final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0); - final boolean wasValidated = nai.lastValidated; - final boolean wasDefault = isDefaultNetwork(nai); - // Only show a connected notification if the network is pending validation - // after the captive portal app was open, and it has now validated. - if (nai.captivePortalValidationPending && valid) { - // User is now logged in, network validated. - nai.captivePortalValidationPending = false; - showNetworkNotification(nai, NotificationType.LOGGED_IN); - } + final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId); + if (nai == null) break; - final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; + handleNetworkTested(nai, results.mTestResult, + (results.mRedirectUrl == null) ? "" : results.mRedirectUrl); - if (DBG) { - final String logMsg = !TextUtils.isEmpty(redirectUrl) - ? " with redirect to " + redirectUrl - : ""; - log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); - } - if (valid != nai.lastValidated) { - if (wasDefault) { - mDeps.getMetricsLogger() - .defaultNetworkMetrics().logDefaultNetworkValidity( - SystemClock.elapsedRealtime(), valid); - } - final int oldScore = nai.getCurrentScore(); - nai.lastValidated = valid; - nai.everValidated |= valid; - updateCapabilities(oldScore, nai, nai.networkCapabilities); - // If score has changed, rebroadcast to NetworkProviders. b/17726566 - if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); - if (valid) { - handleFreshlyValidatedNetwork(nai); - // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and - // LOST_INTERNET notifications if network becomes valid. - mNotifier.clearNotification(nai.network.netId, - NotificationType.NO_INTERNET); - mNotifier.clearNotification(nai.network.netId, - NotificationType.LOST_INTERNET); - mNotifier.clearNotification(nai.network.netId, - NotificationType.PARTIAL_CONNECTIVITY); - mNotifier.clearNotification(nai.network.netId, - NotificationType.PRIVATE_DNS_BROKEN); - // If network becomes valid, the hasShownBroken should be reset for - // that network so that the notification will be fired when the private - // DNS is broken again. - nai.networkAgentConfig.hasShownBroken = false; - } - } else if (partialConnectivityChanged) { - updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); - } - updateInetCondition(nai); - // Let the NetworkAgent know the state of its network - Bundle redirectUrlBundle = new Bundle(); - redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); - // TODO: Evaluate to update partial connectivity to status to NetworkAgent. - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_REPORT_NETWORK_STATUS, - (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), - 0, redirectUrlBundle); - - // If NetworkMonitor detects partial connectivity before - // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification - // immediately. Re-notify partial connectivity silently if no internet - // notification already there. - if (!wasPartial && nai.partialConnectivity) { - // Remove delayed message if there is a pending message. - mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network); - handlePromptUnvalidated(nai.network); - } - - if (wasValidated && !nai.lastValidated) { - handleNetworkUnvalidated(nai); - } + // Invoke ConnectivityReport generation for this Network test event. + final Message m = + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, + new ConnectivityReportEvent(results.mTimestampMillis, nai)); + m.setData(msg.getData()); + mConnectivityDiagnosticsHandler.sendMessage(m); break; } case EVENT_PROVISIONING_NOTIFICATION: { @@ -2879,6 +2833,87 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + private void handleNetworkTested( + @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) { + final boolean wasPartial = nai.partialConnectivity; + nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); + final boolean partialConnectivityChanged = + (wasPartial != nai.partialConnectivity); + + final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0); + final boolean wasValidated = nai.lastValidated; + final boolean wasDefault = isDefaultNetwork(nai); + // Only show a connected notification if the network is pending validation + // after the captive portal app was open, and it has now validated. + if (nai.captivePortalValidationPending && valid) { + // User is now logged in, network validated. + nai.captivePortalValidationPending = false; + showNetworkNotification(nai, NotificationType.LOGGED_IN); + } + + if (DBG) { + final String logMsg = !TextUtils.isEmpty(redirectUrl) + ? " with redirect to " + redirectUrl + : ""; + log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); + } + if (valid != nai.lastValidated) { + if (wasDefault) { + mDeps.getMetricsLogger() + .defaultNetworkMetrics().logDefaultNetworkValidity( + SystemClock.elapsedRealtime(), valid); + } + final int oldScore = nai.getCurrentScore(); + nai.lastValidated = valid; + nai.everValidated |= valid; + updateCapabilities(oldScore, nai, nai.networkCapabilities); + // If score has changed, rebroadcast to NetworkProviders. b/17726566 + if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); + if (valid) { + handleFreshlyValidatedNetwork(nai); + // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and + // LOST_INTERNET notifications if network becomes valid. + mNotifier.clearNotification(nai.network.netId, + NotificationType.NO_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.LOST_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.PARTIAL_CONNECTIVITY); + mNotifier.clearNotification(nai.network.netId, + NotificationType.PRIVATE_DNS_BROKEN); + // If network becomes valid, the hasShownBroken should be reset for + // that network so that the notification will be fired when the private + // DNS is broken again. + nai.networkAgentConfig.hasShownBroken = false; + } + } else if (partialConnectivityChanged) { + updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); + } + updateInetCondition(nai); + // Let the NetworkAgent know the state of its network + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); + // TODO: Evaluate to update partial connectivity to status to NetworkAgent. + nai.asyncChannel.sendMessage( + NetworkAgent.CMD_REPORT_NETWORK_STATUS, + (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), + 0, redirectUrlBundle); + + // If NetworkMonitor detects partial connectivity before + // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification + // immediately. Re-notify partial connectivity silently if no internet + // notification already there. + if (!wasPartial && nai.partialConnectivity) { + // Remove delayed message if there is a pending message. + mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network); + handlePromptUnvalidated(nai.network); + } + + if (wasValidated && !nai.lastValidated) { + handleNetworkUnvalidated(nai); + } + } + private int getCaptivePortalMode() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_MODE, @@ -2927,8 +2962,23 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) { - mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED, - testResult, mNetId, redirectUrl)); + notifyNetworkTestedWithExtras(testResult, redirectUrl, SystemClock.elapsedRealtime(), + PersistableBundle.EMPTY); + } + + @Override + public void notifyNetworkTestedWithExtras( + int testResult, + @Nullable String redirectUrl, + long timestampMillis, + @NonNull PersistableBundle extras) { + final Message msg = + mTrackerHandler.obtainMessage( + EVENT_NETWORK_TESTED, + new NetworkTestedResults( + mNetId, testResult, timestampMillis, redirectUrl)); + msg.setData(new Bundle(extras)); + mTrackerHandler.sendMessage(msg); } @Override @@ -2970,6 +3020,21 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void notifyDataStallSuspected( + long timestampMillis, int detectionMethod, PersistableBundle extras) { + final Message msg = + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, + detectionMethod, mNetId, timestampMillis); + msg.setData(new Bundle(extras)); + + // NetworkStateTrackerHandler currently doesn't take any actions based on data + // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid + // the cost of going through two handlers. + mConnectivityDiagnosticsHandler.sendMessage(msg); + } + + @Override public int getInterfaceVersion() { return this.VERSION; } @@ -4143,6 +4208,19 @@ public class ConnectivityService extends IConnectivityManager.Stub final int connectivityInfo = encodeBool(hasConnectivity); mHandler.sendMessage( mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network)); + + final NetworkAgentInfo nai; + if (network == null) { + nai = getDefaultNetwork(); + } else { + nai = getNetworkAgentInfoForNetwork(network); + } + if (nai != null) { + mConnectivityDiagnosticsHandler.sendMessage( + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED, + connectivityInfo, 0, nai)); + } } private void handleReportNetworkConnectivity( @@ -5229,7 +5307,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // This checks that the passed capabilities either do not request a // specific SSID/SignalStrength, or the calling app has permission to do so. private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc, - int callerPid, int callerUid) { + int callerPid, int callerUid, String callerPackageName) { if (null != nc.getSSID() && !checkSettingsPermission(callerPid, callerUid)) { throw new SecurityException("Insufficient permissions to request a specific SSID"); } @@ -5239,6 +5317,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new SecurityException( "Insufficient permissions to request a specific signal strength"); } + mAppOpsManager.checkPackage(callerUid, callerPackageName); } private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) { @@ -5285,7 +5364,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns); - ns.assertValidFromUid(Binder.getCallingUid()); } private void ensureValid(NetworkCapabilities nc) { @@ -5297,7 +5375,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, int timeoutMs, IBinder binder, int legacyType) { + Messenger messenger, int timeoutMs, IBinder binder, int legacyType, + @NonNull String callingPackageName) { + final int callingUid = Binder.getCallingUid(); final NetworkRequest.Type type = (networkCapabilities == null) ? NetworkRequest.Type.TRACK_DEFAULT : NetworkRequest.Type.REQUEST; @@ -5305,7 +5385,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the default network request. This allows callers to keep track of // the system default network. if (type == NetworkRequest.Type.TRACK_DEFAULT) { - networkCapabilities = createDefaultNetworkCapabilitiesForUid(Binder.getCallingUid()); + networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); enforceAccessPermission(); } else { networkCapabilities = new NetworkCapabilities(networkCapabilities); @@ -5317,13 +5397,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), callingUid, callingPackageName); // Set the UID range for this request to the single UID of the requester, or to an empty // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there // are no visible methods to set the UIDs, an app could use reflection to try and get // networks for other apps so it's essential that the UIDs are overwritten. - restrictRequestUidsForCaller(networkCapabilities); + restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, + callingUid, callingPackageName); if (timeoutMs < 0) { throw new IllegalArgumentException("Bad timeout specified"); @@ -5398,16 +5479,18 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation) { + PendingIntent operation, @NonNull String callingPackageName) { Objects.requireNonNull(operation, "PendingIntent cannot be null."); + final int callingUid = Binder.getCallingUid(); networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), callingUid, callingPackageName); ensureValidNetworkSpecifier(networkCapabilities); - restrictRequestUidsForCaller(networkCapabilities); + restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, + callingUid, callingPackageName); NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); @@ -5455,15 +5538,16 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, IBinder binder) { + Messenger messenger, IBinder binder, @NonNull String callingPackageName) { + final int callingUid = Binder.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); } NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); - restrictRequestUidsForCaller(nc); + Binder.getCallingPid(), callingUid, callingPackageName); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get // onLost and onAvailable callbacks when networks move in and out of the background. @@ -5483,17 +5567,17 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void pendingListenForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation) { + PendingIntent operation, @NonNull String callingPackageName) { Objects.requireNonNull(operation, "PendingIntent cannot be null."); + final int callingUid = Binder.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); } ensureValid(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); - + Binder.getCallingPid(), callingUid, callingPackageName); final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); - restrictRequestUidsForCaller(nc); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); @@ -6506,6 +6590,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork( + @NonNull final NetworkReassignment changes, @NonNull final NetworkAgentInfo newNetwork) { final int score = newNetwork.getCurrentScore(); final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = new ArrayMap<>(); @@ -6516,7 +6601,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // requests or not, and doesn't affect the network's score. if (nri.request.isListen()) continue; - final NetworkAgentInfo currentNetwork = nri.mSatisfier; + // The reassignment has been seeded with the initial assignment, therefore + // getReassignment can't be null and mNewNetwork is only null if there was no + // satisfier in the first place or there was an explicit reassignment to null. + final NetworkAgentInfo currentNetwork = changes.getReassignment(nri).mNewNetwork; final boolean satisfies = newNetwork.satisfies(nri.request); if (newNetwork == currentNetwork && satisfies) continue; @@ -6566,7 +6654,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) log("rematching " + newNetwork.name()); final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = - computeRequestReassignmentForNetwork(newNetwork); + computeRequestReassignmentForNetwork(changes, newNetwork); // Find and migrate to this Network any NetworkRequests for // which this network is now the best. @@ -7373,7 +7461,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private Vpn getVpnIfOwner() { - final int uid = Binder.getCallingUid(); + return getVpnIfOwner(Binder.getCallingUid()); + } + + @GuardedBy("mVpns") + private Vpn getVpnIfOwner(int uid) { final int user = UserHandle.getUserId(uid); final Vpn vpn = mVpns.get(user); @@ -7469,6 +7561,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @VisibleForTesting class ConnectivityDiagnosticsHandler extends Handler { + private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName(); + /** * Used to handle ConnectivityDiagnosticsCallback registration events from {@link * android.net.ConnectivityDiagnosticsManager}. @@ -7485,6 +7579,37 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2; + /** + * Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks + * after processing {@link #EVENT_NETWORK_TESTED} events. + * obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from + * NetworkMonitor. + * data = PersistableBundle of extras passed from NetworkMonitor. + * + * <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}. + */ + private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED; + + /** + * Event for NetworkMonitor to inform ConnectivityService that a potential data stall has + * been detected on the network. + * obj = Long the timestamp (in millis) for when the suspected data stall was detected. + * arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method. + * arg2 = NetID. + * data = PersistableBundle of extras passed from NetworkMonitor. + */ + private static final int EVENT_DATA_STALL_SUSPECTED = 4; + + /** + * Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to + * the platform. This event will invoke {@link + * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned + * callbacks. + * obj = Network that was reported on + * arg1 = boolint for the quality reported + */ + private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5; + private ConnectivityDiagnosticsHandler(Looper looper) { super(looper); } @@ -7502,6 +7627,37 @@ public class ConnectivityService extends IConnectivityManager.Stub (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1); break; } + case EVENT_NETWORK_TESTED: { + final ConnectivityReportEvent reportEvent = + (ConnectivityReportEvent) msg.obj; + + // This is safe because {@link + // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a + // PersistableBundle and converts it to the Bundle in the incoming Message. If + // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will + // not be set. This is also safe, as msg.getData() will return an empty Bundle. + final PersistableBundle extras = new PersistableBundle(msg.getData()); + handleNetworkTestedWithExtras(reportEvent, extras); + break; + } + case EVENT_DATA_STALL_SUSPECTED: { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); + if (nai == null) break; + + // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected + // receives a PersistableBundle and converts it to the Bundle in the incoming + // Message. + final PersistableBundle extras = new PersistableBundle(msg.getData()); + handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras); + break; + } + case EVENT_NETWORK_CONNECTIVITY_REPORTED: { + handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1)); + break; + } + default: { + Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what); + } } } } @@ -7511,12 +7667,16 @@ public class ConnectivityService extends IConnectivityManager.Stub class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient { @NonNull private final IConnectivityDiagnosticsCallback mCb; @NonNull private final NetworkRequestInfo mRequestInfo; + @NonNull private final String mCallingPackageName; @VisibleForTesting ConnectivityDiagnosticsCallbackInfo( - @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) { + @NonNull IConnectivityDiagnosticsCallback cb, + @NonNull NetworkRequestInfo nri, + @NonNull String callingPackageName) { mCb = cb; mRequestInfo = nri; + mCallingPackageName = callingPackageName; } @Override @@ -7526,6 +7686,39 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Class used for sending information from {@link + * NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} to the handler for processing it. + */ + private static class NetworkTestedResults { + private final int mNetId; + private final int mTestResult; + private final long mTimestampMillis; + @Nullable private final String mRedirectUrl; + + private NetworkTestedResults( + int netId, int testResult, long timestampMillis, @Nullable String redirectUrl) { + mNetId = netId; + mTestResult = testResult; + mTimestampMillis = timestampMillis; + mRedirectUrl = redirectUrl; + } + } + + /** + * Class used for sending information from {@link NetworkStateTrackerHandler} to {@link + * ConnectivityDiagnosticsHandler}. + */ + private static class ConnectivityReportEvent { + private final long mTimestampMillis; + @NonNull private final NetworkAgentInfo mNai; + + private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) { + mTimestampMillis = timestampMillis; + mNai = nai; + } + } + private void handleRegisterConnectivityDiagnosticsCallback( @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) { ensureRunningOnConnectivityServiceThread(); @@ -7573,18 +7766,115 @@ public class ConnectivityService extends IConnectivityManager.Stub cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0); } + private void handleNetworkTestedWithExtras( + @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) { + final NetworkAgentInfo nai = reportEvent.mNai; + final ConnectivityReport report = + new ConnectivityReport( + reportEvent.mNai.network, + reportEvent.mTimestampMillis, + nai.linkProperties, + nai.networkCapabilities, + extras); + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onConnectivityReport(report); + } catch (RemoteException ex) { + loge("Error invoking onConnectivityReport", ex); + } + } + } + + private void handleDataStallSuspected( + @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod, + @NonNull PersistableBundle extras) { + final DataStallReport report = + new DataStallReport(nai.network, timestampMillis, detectionMethod, extras); + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onDataStallSuspected(report); + } catch (RemoteException ex) { + loge("Error invoking onDataStallSuspected", ex); + } + } + } + + private void handleNetworkConnectivityReported( + @NonNull NetworkAgentInfo nai, boolean connectivity) { + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onNetworkConnectivityReported(nai.network, connectivity); + } catch (RemoteException ex) { + loge("Error invoking onNetworkConnectivityReported", ex); + } + } + } + + private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks( + @NonNull NetworkAgentInfo nai) { + final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>(); + for (Entry<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> entry : + mConnectivityDiagnosticsCallbacks.entrySet()) { + final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue(); + final NetworkRequestInfo nri = cbInfo.mRequestInfo; + if (nai.satisfies(nri.request)) { + if (checkConnectivityDiagnosticsPermissions( + nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) { + results.add(entry.getKey()); + } + } + } + return results; + } + + @VisibleForTesting + boolean checkConnectivityDiagnosticsPermissions( + int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) { + if (checkNetworkStackPermission(callbackPid, callbackUid)) { + return true; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + callbackPackageName, null /* featureId */, callbackUid, null /* message */)) { + return false; + } + + synchronized (mVpns) { + if (getVpnIfOwner(callbackUid) != null) { + return true; + } + } + + // Administrator UIDs also contains the Owner UID + if (nai.networkCapabilities.getAdministratorUids().contains(callbackUid)) { + return true; + } + + return false; + } + @Override public void registerConnectivityDiagnosticsCallback( - @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) { + @NonNull IConnectivityDiagnosticsCallback callback, + @NonNull NetworkRequest request, + @NonNull String callingPackageName) { if (request.legacyType != TYPE_NONE) { throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated." + " Please use NetworkCapabilities instead."); } + final int callingUid = Binder.getCallingUid(); + mAppOpsManager.checkPackage(callingUid, callingPackageName); // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid // and administrator uids to be safe. final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities); - restrictRequestUidsForCaller(nc); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); final NetworkRequest requestWithId = new NetworkRequest( @@ -7597,7 +7887,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // callback's binder death. final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId); final ConnectivityDiagnosticsCallbackInfo cbInfo = - new ConnectivityDiagnosticsCallbackInfo(callback, nri); + new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName); mConnectivityDiagnosticsHandler.sendMessage( mConnectivityDiagnosticsHandler.obtainMessage( diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 50843b0999a1..63cddac0bfba 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -30,6 +30,7 @@ import static android.os.PowerManager.locationPowerSaveModeToString; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -305,11 +306,7 @@ public class LocationManagerService extends ILocationManager.Stub { public void onOpChanged(int op, String packageName) { // onOpChanged invoked on ui thread, move to our thread to reduce risk // of blocking ui thread - mHandler.post(() -> { - synchronized (mLock) { - onAppOpChangedLocked(); - } - }); + mHandler.post(() -> onAppOpChanged(packageName)); } }); mPackageManager.addOnPermissionsChangeListener( @@ -392,13 +389,26 @@ public class LocationManagerService extends ILocationManager.Stub { } } - @GuardedBy("mLock") - private void onAppOpChangedLocked() { - for (Receiver receiver : mReceivers.values()) { - receiver.updateMonitoring(true); - } - for (LocationProviderManager manager : mProviderManagers) { - applyRequirementsLocked(manager); + private void onAppOpChanged(String packageName) { + synchronized (mLock) { + for (Receiver receiver : mReceivers.values()) { + if (receiver.mCallerIdentity.mPackageName.equals(packageName)) { + receiver.updateMonitoring(true); + } + } + + HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); + for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + String provider = entry.getKey(); + for (UpdateRecord record : entry.getValue()) { + if (record.mReceiver.mCallerIdentity.mPackageName.equals(packageName)) { + affectedProviders.add(provider); + } + } + } + for (String provider : affectedProviders) { + applyRequirementsLocked(provider); + } } } @@ -2161,10 +2171,10 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean injectLocation(Location location) { - mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, - "Location Hardware permission not granted to inject location"); - mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, - "Access Fine Location permission not granted to inject Location"); + mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null); + mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, null); + + Preconditions.checkArgument(location.isComplete()); synchronized (mLock) { LocationProviderManager manager = getLocationProviderManager(location.getProvider()); @@ -2410,20 +2420,15 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean isLocationEnabledForUser(int userId) { - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, - null); - } - + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, "isLocationEnabledForUser", null); return mSettingsHelper.isLocationEnabled(userId); } @Override public boolean isProviderEnabledForUser(String providerName, int userId) { - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, - null); - } + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, "isProviderEnabledForUser", null); // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use @@ -2732,6 +2737,9 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void setTestProviderLocation(String provider, Location location, String packageName) { + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) != AppOpsManager.MODE_ALLOWED) { return; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 4a65a96e57a1..fabe92dbcb8a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -287,7 +287,7 @@ final class ActivityManagerConstants extends ContentObserver { // When the restriction is enabled, foreground service started from background will not have // while-in-use permissions like location, camera and microphone. (The foreground service can be // started, the restriction is on while-in-use permissions.) - volatile boolean mFlagBackgroundFgsStartRestrictionEnabled; + volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true; private final ActivityManagerService mService; private ContentResolver mResolver; @@ -304,18 +304,19 @@ final class ActivityManagerConstants extends ContentObserver { // we have no limit on the number of service, visible, foreground, or other such // processes and the number of those processes does not count against the cached // process limit. - public int CUR_MAX_CACHED_PROCESSES; + public int CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; // The maximum number of empty app processes we will let sit around. - public int CUR_MAX_EMPTY_PROCESSES; + public int CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); // The number of empty apps at which we don't consider it necessary to do // memory trimming. - public int CUR_TRIM_EMPTY_PROCESSES; + public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2; // The number of cached at which we don't consider it necessary to do // memory trimming. - public int CUR_TRIM_CACHED_PROCESSES; + public int CUR_TRIM_CACHED_PROCESSES = + (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3; /** * Packages that can't be killed even if it's requested to be killed on imperceptible. @@ -419,6 +420,8 @@ final class ActivityManagerConstants extends ContentObserver { context.getResources().getIntArray( com.android.internal.R.array.config_defaultImperceptibleKillingExemptionProcStates)) .boxed().collect(Collectors.toList()); + IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); + IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates); } public void start(ContentResolver resolver) { @@ -550,8 +553,6 @@ final class ActivityManagerConstants extends ContentObserver { // For new flags that are intended for server-side experiments, please use the new // DeviceConfig package. - - updateMaxCachedProcesses(); } } @@ -580,6 +581,9 @@ final class ActivityManagerConstants extends ContentObserver { } private void updateOomAdjUpdatePolicy() { + + + OOMADJ_UPDATE_QUICK = DeviceConfig.getInt( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_OOMADJ_UPDATE_POLICY, diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index b19a37e0d840..f872c6bd1b8b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -50,7 +50,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; static final boolean DEBUG_COMPACTION = DEBUG_ALL || false; - static final boolean DEBUG_FREEZER = DEBUG_ALL || false; + static final boolean DEBUG_FREEZER = DEBUG_ALL || true; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 148f7de4449f..3ad96ea193bf 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2617,7 +2617,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessCpuThread.start(); mBatteryStatsService.publish(); - mAppOpsService.publish(mContext); + mAppOpsService.publish(); Slog.d("AppOps", "AppOpsService published"); LocalServices.addService(ActivityManagerInternal.class, mInternal); mActivityTaskManager.onActivityManagerInternalAdded(); @@ -6843,7 +6843,8 @@ public class ActivityManagerService extends IActivityManager.Stub cpi = cpr.info; if (isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) - && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) { + && isValidSingletonCall(r == null ? callingUid : r.uid, + cpi.applicationInfo.uid)) { userId = UserHandle.USER_SYSTEM; checkCrossUser = false; } else { @@ -6931,7 +6932,8 @@ public class ActivityManagerService extends IActivityManager.Stub conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, stable); if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { - if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { + if (cpr.proc != null + && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { // If this is a perceptible app accessing the provider, // make sure to count it as being accessed and thus // back up on the LRU list. This is good because @@ -7003,7 +7005,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Then allow connecting to the singleton provider boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) - && isValidSingletonCall(r.uid, cpi.applicationInfo.uid); + && isValidSingletonCall(r == null ? callingUid : r.uid, + cpi.applicationInfo.uid); if (singleton) { userId = UserHandle.USER_SYSTEM; } @@ -19507,7 +19510,7 @@ public class ActivityManagerService extends IActivityManager.Stub } public AppOpsService getAppOpsService(File file, Handler handler) { - return new AppOpsService(file, handler); + return new AppOpsService(file, handler, getContext()); } public Handler getUiHandler(ActivityManagerService service) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 313c185a84c6..d047a3ca993c 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -442,7 +442,7 @@ public final class CachedAppOptimizer { */ @GuardedBy("mPhenotypeFlagLock") private void updateUseFreezer() { - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { mUseFreezer = isFreezerSupported(); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index b107626e3bec..a651d9dc4992 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -330,6 +330,7 @@ public final class OomAdjuster { // If this proc state is changed, need to update its uid record here if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT && (uidRec.setProcState != uidRec.getCurProcState() + || uidRec.setCapability != uidRec.curCapability || uidRec.setWhitelist != uidRec.curWhitelist)) { ActiveUids uids = mTmpUidRecords; uids.clear(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 71486d3686f3..dcada89bb04a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3271,6 +3271,9 @@ public final class ProcessList { } final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) { + if (thread == null) { + return null; + } final IBinder threadBinder = thread.asBinder(); // Find the application record. for (int i = mLruProcesses.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 784ce4a7d53b..06561f57f495 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -19,11 +19,15 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.AppOpsManager.CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE; import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; import static android.app.AppOpsManager.HistoricalOpsRequestFilter; +import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; @@ -41,6 +45,7 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -53,6 +58,10 @@ import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.os.Process.STATSD_UID; +import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; + +import static java.lang.Long.max; + import android.Manifest; import android.annotation.IntRange; import android.annotation.NonNull; @@ -70,6 +79,7 @@ import android.app.AppOpsManager.OpFlags; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.app.AsyncNotedAppOp; +import android.compat.Compatibility; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -129,7 +139,6 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -216,7 +225,7 @@ public class AppOpsService extends IAppOpsService.Stub { //TODO: remove this when development is done. private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; - Context mContext; + final Context mContext; final AtomicFile mFile; final Handler mHandler; @@ -291,8 +300,11 @@ public class AppOpsService extends IAppOpsService.Stub { @GuardedBy("this") private CheckOpsDelegate mCheckOpsDelegate; - @GuardedBy("this") - private SparseArray<List<Integer>> mSwitchOpToOps; + /** + * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never + * changed + */ + private final SparseArray<int[]> mSwitchedOps = new SparseArray<>(); private ActivityManagerInternal mActivityManagerInternal; @@ -343,30 +355,25 @@ public class AppOpsService extends IAppOpsService.Stub { */ @VisibleForTesting final class Constants extends ContentObserver { - // Key names stored in the settings value. - private static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; - private static final String KEY_FG_SERVICE_STATE_SETTLE_TIME - = "fg_service_state_settle_time"; - private static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; /** * How long we want for a drop in uid state from top to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_TOP_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME */ public long TOP_STATE_SETTLE_TIME; /** * How long we want for a drop in uid state from foreground to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_FG_SERVICE_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME */ public long FG_SERVICE_STATE_SETTLE_TIME; /** * How long we want for a drop in uid state from background to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_BG_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME */ public long BG_STATE_SETTLE_TIME; @@ -1191,17 +1198,22 @@ public class AppOpsService extends IAppOpsService.Stub { final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); final class ModeCallback implements DeathRecipient { + /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ + public static final int ALL_OPS = -2; + final IAppOpsCallback mCallback; final int mWatchingUid; final int mFlags; + final int mWatchedOpCode; final int mCallingUid; final int mCallingPid; - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int callingUid, - int callingPid) { + ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp, + int callingUid, int callingPid) { mCallback = callback; mWatchingUid = watchingUid; mFlags = flags; + mWatchedOpCode = watchedOp; mCallingUid = callingUid; mCallingPid = callingPid; try { @@ -1224,6 +1236,10 @@ public class AppOpsService extends IAppOpsService.Stub { UserHandle.formatUid(sb, mWatchingUid); sb.append(" flags=0x"); sb.append(Integer.toHexString(mFlags)); + if (mWatchedOpCode != OP_NONE) { + sb.append(" op="); + sb.append(opToName(mWatchedOpCode)); + } sb.append(" from uid="); UserHandle.formatUid(sb, mCallingUid); sb.append(" pid="); @@ -1337,16 +1353,23 @@ public class AppOpsService extends IAppOpsService.Stub { featureOp.onClientDeath(clientId); } - public AppOpsService(File storagePath, Handler handler) { + public AppOpsService(File storagePath, Handler handler, Context context) { + mContext = context; + LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath, "appops"); mHandler = handler; mConstants = new Constants(mHandler); readState(); + + for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { + int switchCode = AppOpsManager.opToSwitch(switchedCode); + mSwitchedOps.put(switchCode, + ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); + } } - public void publish(Context context) { - mContext = context; + public void publish() { ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder()); LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal); } @@ -1616,6 +1639,19 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * Update the pending state for the uid + * + * @param currentTime The current elapsed real time + * @param uid The uid that has a pending state + */ + private void updatePendingState(long currentTime, int uid) { + synchronized (this) { + mLastRealtime = max(currentTime, mLastRealtime); + updatePendingStateIfNeededLocked(mUidStates.get(uid)); + } + } + public void updateUidProcState(int uid, int procState, @ActivityManager.ProcessCapability int capability) { synchronized (this) { @@ -1647,7 +1683,12 @@ public class AppOpsService extends IAppOpsService.Stub { } else { settleTime = mConstants.BG_STATE_SETTLE_TIME; } - uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime; + final long commitTime = SystemClock.elapsedRealtime() + settleTime; + uidState.pendingStateCommitTime = commitTime; + + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(AppOpsService::updatePendingState, this, + commitTime + 1, uid), settleTime + 1); } if (uidState.pkgOps != null) { @@ -2014,6 +2055,19 @@ public class AppOpsService extends IAppOpsService.Stub { uidState.evalForegroundOps(mOpModeWatchers); } + notifyOpChangedForAllPkgsInUid(code, uid, false, callbackToIgnore); + notifyOpChangedSync(code, uid, null, mode); + } + + /** + * Notify that an op changed for all packages in an uid. + * + * @param code The op that changed + * @param uid The uid the op was changed for + * @param onlyForeground Only notify watchers that watch for foreground changes + */ + private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, + @Nullable IAppOpsCallback callbackToIgnore) { String[] uidPackageNames = getPackagesForUid(uid); ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null; @@ -2023,6 +2077,10 @@ public class AppOpsService extends IAppOpsService.Stub { final int callbackCount = callbacks.size(); for (int i = 0; i < callbackCount; i++) { ModeCallback callback = callbacks.valueAt(i); + if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + ArraySet<String> changedPackages = new ArraySet<>(); Collections.addAll(changedPackages, uidPackageNames); if (callbackSpecs == null) { @@ -2041,6 +2099,10 @@ public class AppOpsService extends IAppOpsService.Stub { final int callbackCount = callbacks.size(); for (int i = 0; i < callbackCount; i++) { ModeCallback callback = callbacks.valueAt(i); + if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + ArraySet<String> changedPackages = callbackSpecs.get(callback); if (changedPackages == null) { changedPackages = new ArraySet<>(); @@ -2057,7 +2119,6 @@ public class AppOpsService extends IAppOpsService.Stub { } if (callbackSpecs == null) { - notifyOpChangedSync(code, uid, null, mode); return; } @@ -2079,23 +2140,24 @@ public class AppOpsService extends IAppOpsService.Stub { } } } - - notifyOpChangedSync(code, uid, null, mode); } private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { PackageManager packageManager = mContext.getPackageManager(); + if (packageManager == null) { + // This can only happen during early boot. At this time the permission state and appop + // state are in sync + return; + } + String[] packageNames = packageManager.getPackagesForUid(uid); if (ArrayUtils.isEmpty(packageNames)) { return; } String packageName = packageNames[0]; - List<Integer> ops = getSwitchOpToOps().get(switchCode); - int opsSize = CollectionUtils.size(ops); - for (int i = 0; i < opsSize; i++) { - int code = ops.get(i); - + int[] ops = mSwitchedOps.get(switchCode); + for (int code : ops) { String permissionName = AppOpsManager.opToPermission(code); if (permissionName == null) { continue; @@ -2126,24 +2188,27 @@ public class AppOpsService extends IAppOpsService.Stub { UserHandle user = UserHandle.getUserHandleForUid(uid); boolean isRevokedCompat; if (permissionInfo.backgroundPermission != null) { - boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; - - if (isBackgroundRevokedCompat && supportsRuntimePermissions) { - Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" - + " permission state, this is discouraged and you should revoke the" - + " runtime permission instead: uid=" + uid + ", switchCode=" - + switchCode + ", mode=" + mode + ", permission=" - + permissionInfo.backgroundPermission); - } + if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName) + == PackageManager.PERMISSION_GRANTED) { + boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; + + if (isBackgroundRevokedCompat && supportsRuntimePermissions) { + Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" + + " permission state, this is discouraged and you should revoke the" + + " runtime permission instead: uid=" + uid + ", switchCode=" + + switchCode + ", mode=" + mode + ", permission=" + + permissionInfo.backgroundPermission); + } - long identity = Binder.clearCallingIdentity(); - try { - packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, - packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, - isBackgroundRevokedCompat - ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); - } finally { - Binder.restoreCallingIdentity(identity); + long identity = Binder.clearCallingIdentity(); + try { + packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, + packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + isBackgroundRevokedCompat + ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); + } finally { + Binder.restoreCallingIdentity(identity); + } } isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED @@ -2170,25 +2235,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - @NonNull - private SparseArray<List<Integer>> getSwitchOpToOps() { - synchronized (this) { - if (mSwitchOpToOps == null) { - mSwitchOpToOps = new SparseArray<>(); - for (int op = 0; op < _NUM_OP; op++) { - int switchOp = AppOpsManager.opToSwitch(op); - List<Integer> ops = mSwitchOpToOps.get(switchOp); - if (ops == null) { - ops = new ArrayList<>(); - mSwitchOpToOps.put(switchOp, ops); - } - ops.add(op); - } - } - return mSwitchOpToOps; - } - } - private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) { final StorageManagerInternal storageManagerInternal = LocalServices.getService(StorageManagerInternal.class); @@ -2289,16 +2335,29 @@ public class AppOpsService extends IAppOpsService.Stub { if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { return; } - // There are features watching for mode changes such as window manager - // and location manager which are in our process. The callbacks in these - // features may require permissions our remote caller does not have. - final long identity = Binder.clearCallingIdentity(); - try { - callback.mCallback.opChanged(code, uid, packageName); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(identity); + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int[] switchedCodes; + if (callback.mWatchedOpCode == ALL_OPS) { + switchedCodes = mSwitchedOps.get(code); + } else if (callback.mWatchedOpCode == OP_NONE) { + switchedCodes = new int[]{code}; + } else { + switchedCodes = new int[]{callback.mWatchedOpCode}; + } + + for (int switchedCode : switchedCodes) { + // There are features watching for mode changes such as window manager + // and location manager which are in our process. The callbacks in these + // features may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + callback.mCallback.opChanged(switchedCode, uid, packageName); + } catch (RemoteException e) { + /* ignore */ + } finally { + Binder.restoreCallingIdentity(identity); + } } } @@ -2493,17 +2552,32 @@ public class AppOpsService extends IAppOpsService.Stub { return; } synchronized (this) { - op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; + int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int notifiedOps; + if (Compatibility.isChangeEnabled( + CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE)) { + if (op == OP_NONE) { + notifiedOps = ALL_OPS; + } else { + notifiedOps = op; + } + } else { + notifiedOps = switchOp; + } + ModeCallback cb = mModeWatchers.get(callback.asBinder()); if (cb == null) { - cb = new ModeCallback(callback, watchedUid, flags, callingUid, callingPid); + cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid, + callingPid); mModeWatchers.put(callback.asBinder(), cb); } - if (op != AppOpsManager.OP_NONE) { - ArraySet<ModeCallback> cbs = mOpModeWatchers.get(op); + if (switchOp != AppOpsManager.OP_NONE) { + ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp); if (cbs == null) { cbs = new ArraySet<>(); - mOpModeWatchers.put(op, cbs); + mOpModeWatchers.put(switchOp, cbs); } cbs.add(cb); } @@ -3322,6 +3396,18 @@ public class AppOpsService extends IAppOpsService.Stub { uidState = new UidState(uid); mUidStates.put(uid, uidState); } else { + updatePendingStateIfNeededLocked(uidState); + } + return uidState; + } + + /** + * Check if the pending state should be updated and do so if needed + * + * @param uidState The uidState that might have a pending state + */ + private void updatePendingStateIfNeededLocked(@NonNull UidState uidState) { + if (uidState != null) { if (uidState.pendingStateCommitTime != 0) { if (uidState.pendingStateCommitTime < mLastRealtime) { commitUidPendingStateLocked(uidState); @@ -3333,7 +3419,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } } - return uidState; } private void commitUidPendingStateLocked(UidState uidState) { @@ -3353,24 +3438,28 @@ public class AppOpsService extends IAppOpsService.Stub { && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) { continue; } - final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { - final ModeCallback callback = callbacks.valueAt(cbi); - if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !callback.isWatchingUid(uidState.uid)) { - continue; - } - boolean doAllPackages = uidState.opModes != null - && uidState.opModes.indexOfKey(code) >= 0 - && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND; - if (uidState.pkgOps != null) { + + if (uidState.opModes != null + && uidState.opModes.indexOfKey(code) >= 0 + && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChangedForAllPkgsInUid, + this, code, uidState.uid, true, null)); + } else { + final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); + if (callbacks != null) { + for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { + final ModeCallback callback = callbacks.valueAt(cbi); + if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !callback.isWatchingUid(uidState.uid)) { + continue; + } for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { final Op op = uidState.pkgOps.valueAt(pkgi).get(code); if (op == null) { continue; } - if (doAllPackages || op.mode == AppOpsManager.MODE_FOREGROUND) { + if (op.mode == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChanged, this, callback, code, uidState.uid, @@ -3795,11 +3884,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (tagName.equals("op")) { final int code = Integer.parseInt(parser.getAttributeValue(null, "n")); final int mode = Integer.parseInt(parser.getAttributeValue(null, "m")); - UidState uidState = getUidStateLocked(uid, true); - if (uidState.opModes == null) { - uidState.opModes = new SparseIntArray(); - } - uidState.opModes.put(code, mode); + setUidMode(code, uid, mode); } else { Slog.w(TAG, "Unknown element under <uid-ops>: " + parser.getName()); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b2548af0f576..82a2f01d4857 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3406,13 +3406,11 @@ public class AudioService extends IAudioService.Stub } public void binderDied() { - int oldModeOwnerPid = 0; + int oldModeOwnerPid; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); - if (!mSetModeDeathHandlers.isEmpty()) { - oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); - } + oldModeOwnerPid = getModeOwnerPid(); int index = mSetModeDeathHandlers.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered setMode() client died"); @@ -3446,17 +3444,19 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMode(int) */ public void setMode(int mode, IBinder cb, String callingPackage) { - if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); } + if (DEBUG_MODE) { + Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); + } if (!checkAudioSettingsPermission("setMode()")) { return; } - - if ( (mode == AudioSystem.MODE_IN_CALL) && - (mContext.checkCallingOrSelfPermission( + final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) - != PackageManager.PERMISSION_GRANTED)) { + == PackageManager.PERMISSION_GRANTED; + final int callingPid = Binder.getCallingPid(); + if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + + callingPid + ", uid=" + Binder.getCallingUid()); return; } @@ -3470,16 +3470,25 @@ public class AudioService extends IAudioService.Stub return; } - int oldModeOwnerPid = 0; - int newModeOwnerPid = 0; + int oldModeOwnerPid; + int newModeOwnerPid; synchronized (mDeviceBroker.mSetModeLock) { - if (!mSetModeDeathHandlers.isEmpty()) { - oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); - } if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } - newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); + oldModeOwnerPid = getModeOwnerPid(); + // Do not allow changing mode if a call is active and the requester + // does not have permission to modify phone state or is not the mode owner. + if (((mMode == AudioSystem.MODE_IN_CALL) + || (mMode == AudioSystem.MODE_IN_COMMUNICATION)) + && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) { + Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid + + ", uid=" + Binder.getCallingUid() + + ", cannot change mode from " + mMode + + " without permission or being mode owner"); + return; + } + newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage); } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes @@ -3569,10 +3578,9 @@ public class AudioService extends IAudioService.Stub if (status == AudioSystem.AUDIO_STATUS_OK) { if (actualMode != AudioSystem.MODE_NORMAL) { - if (mSetModeDeathHandlers.isEmpty()) { + newModeOwnerPid = getModeOwnerPid(); + if (newModeOwnerPid == 0) { Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack"); - } else { - newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } } // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 0d88388742d2..0f549842a763 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -99,6 +99,33 @@ public class AuthService extends SystemService { public String[] getConfiguration(Context context) { return context.getResources().getStringArray(R.array.config_biometric_sensors); } + + /** + * Allows us to mock FingerprintService for testing + */ + @VisibleForTesting + public IFingerprintService getFingerprintService() { + return IFingerprintService.Stub.asInterface( + ServiceManager.getService(Context.FINGERPRINT_SERVICE)); + } + + /** + * Allows us to mock FaceService for testing + */ + @VisibleForTesting + public IFaceService getFaceService() { + return IFaceService.Stub.asInterface( + ServiceManager.getService(Context.FACE_SERVICE)); + } + + /** + * Allows us to mock IrisService for testing + */ + @VisibleForTesting + public IIrisService getIrisService() { + return IIrisService.Stub.asInterface( + ServiceManager.getService(Context.IRIS_SERVICE)); + } } private final class AuthServiceImpl extends IAuthService.Stub { @@ -178,7 +205,6 @@ public class AuthService extends SystemService { mInjector = injector; mImpl = new AuthServiceImpl(); - final PackageManager pm = context.getPackageManager(); } private void registerAuthenticator(SensorConfig config) throws RemoteException { @@ -191,18 +217,36 @@ public class AuthService extends SystemService { switch (config.mModality) { case TYPE_FINGERPRINT: - authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface( - ServiceManager.getService(Context.FINGERPRINT_SERVICE))); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService == null) { + Slog.e(TAG, "Attempting to register with null FingerprintService. Please check" + + " your device configuration."); + return; + } + + authenticator = new FingerprintAuthenticator(fingerprintService); break; case TYPE_FACE: - authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface( - ServiceManager.getService(Context.FACE_SERVICE))); + final IFaceService faceService = mInjector.getFaceService(); + if (faceService == null) { + Slog.e(TAG, "Attempting to register with null FaceService. Please check your" + + " device configuration."); + return; + } + + authenticator = new FaceAuthenticator(faceService); break; case TYPE_IRIS: - authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface( - ServiceManager.getService(Context.IRIS_SERVICE))); + final IIrisService irisService = mInjector.getIrisService(); + if (irisService == null) { + Slog.e(TAG, "Attempting to register with null IrisService. Please check your" + + " device configuration."); + return; + } + + authenticator = new IrisAuthenticator(irisService); break; default: diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 766e5c4d638f..7bbda9f3f732 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -20,11 +20,14 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.security.KeyStore; import android.util.Slog; +import java.io.IOException; import java.util.ArrayList; /** @@ -41,6 +44,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public static final int LOCKOUT_PERMANENT = 2; private final boolean mRequireConfirmation; + private final NativeHandle mWindowId; // We need to track this state since it's possible for applications to request for // authentication while the device is already locked out. In that case, the client is created @@ -69,11 +73,25 @@ public abstract class AuthenticationClient extends ClientMonitor { public AuthenticationClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId, restricted, owner, cookie); mOpId = opId; mRequireConfirmation = requireConfirmation; + mWindowId = Utils.dupNativeHandle(windowId); + } + + @Override + public void destroy() { + if (mWindowId != null && mWindowId.getFileDescriptors() != null) { + try { + mWindowId.close(); + } catch (IOException e) { + Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e); + } + } + super.destroy(); } protected long getStartTimeMs() { @@ -233,7 +251,7 @@ public abstract class AuthenticationClient extends ClientMonitor { onStart(); try { mStartTimeMs = System.currentTimeMillis(); - final int result = getDaemonWrapper().authenticate(mOpId, getGroupId()); + final int result = getDaemonWrapper().authenticate(mOpId, getGroupId(), mWindowId); if (result != 0) { Slog.w(getLogTag(), "startAuthentication failed, result=" + result); mMetricsLogger.histogram(mConstants.tagAuthStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 687d9353ebd1..0e709944b03f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -32,6 +32,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; import android.os.IRemoteCallback; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -220,9 +222,10 @@ public abstract class BiometricServiceBase extends SystemService public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId, - groupId, opId, restricted, owner, cookie, requireConfirmation); + groupId, opId, restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -283,10 +286,10 @@ public abstract class BiometricServiceBase extends SystemService public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, - final int[] disabledFeatures, int timeoutSec) { + final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) { super(context, getConstants(), daemon, halDeviceId, token, listener, userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), - disabledFeatures, timeoutSec); + disabledFeatures, timeoutSec, windowId); } @Override @@ -472,12 +475,13 @@ public abstract class BiometricServiceBase extends SystemService */ protected interface DaemonWrapper { int ERROR_ESRCH = 3; // Likely HAL is dead. see errno.h. - int authenticate(long operationId, int groupId) throws RemoteException; + int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException; int cancel() throws RemoteException; int remove(int groupId, int biometricId) throws RemoteException; int enumerate() throws RemoteException; int enroll(byte[] token, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException; + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException; void resetLockout(byte[] token) throws RemoteException; } diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index 7ebb7c059b4c..684795ec66b5 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -20,10 +20,13 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.util.Slog; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +38,7 @@ public abstract class EnrollClient extends ClientMonitor { private final BiometricUtils mBiometricUtils; private final int[] mDisabledFeatures; private final int mTimeoutSec; + private final NativeHandle mWindowId; private long mEnrollmentStartTimeMs; @@ -44,13 +48,26 @@ public abstract class EnrollClient extends ClientMonitor { BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils, - final int[] disabledFeatures, int timeoutSec) { + final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) { super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mTimeoutSec = timeoutSec; + mWindowId = Utils.dupNativeHandle(windowId); + } + + @Override + public void destroy() { + if (mWindowId != null && mWindowId.getFileDescriptors() != null) { + try { + mWindowId.close(); + } catch (IOException e) { + Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e); + } + } + super.destroy(); } @Override @@ -102,7 +119,7 @@ public abstract class EnrollClient extends ClientMonitor { } final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), mTimeoutSec, - disabledFeatures); + disabledFeatures, mWindowId); if (result != 0) { Slog.w(getLogTag(), "startEnroll failed, result=" + result); mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 389763b5377a..2d4ab6308a8b 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -23,12 +23,17 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.Build; import android.os.Bundle; +import android.os.NativeHandle; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; +import java.io.FileDescriptor; +import java.io.IOException; + public class Utils { public static boolean isDebugEnabled(Context context, int targetUserId) { if (targetUserId == UserHandle.USER_NULL) { @@ -237,4 +242,31 @@ public class Utils { throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); } } + + /** + * Converts an {@link IBiometricNativeHandle} to a {@link NativeHandle} by duplicating the + * the underlying file descriptors. + * + * Both the original and new handle must be closed after use. + * + * @param h {@link IBiometricNativeHandle} received as a binder call argument. Usually used to + * identify a WindowManager window. Can be null. + * @return A {@link NativeHandle} representation of {@code h}. Will be null if either {@code h} + * or its contents are null. + */ + public static NativeHandle dupNativeHandle(IBiometricNativeHandle h) { + NativeHandle handle = null; + if (h != null && h.fds != null && h.ints != null) { + FileDescriptor[] fds = new FileDescriptor[h.fds.length]; + for (int i = 0; i < h.fds.length; ++i) { + try { + fds[i] = h.fds[i].dup().getFileDescriptor(); + } catch (IOException e) { + return null; + } + } + handle = new NativeHandle(fds, h.ints, true /* own */); + } + return handle; + } } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index b512475e7971..31c3d4d7b24e 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -34,6 +34,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.face.V1_0.IBiometricsFace; @@ -214,9 +215,10 @@ public class FaceService extends BiometricServiceBase { public FaceAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, cookie, requireConfirmation); + restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -373,7 +375,7 @@ public class FaceService extends BiometricServiceBase { @Override // Binder call public void enroll(int userId, final IBinder token, final byte[] cryptoToken, final IFaceServiceReceiver receiver, final String opPackageName, - final int[] disabledFeatures) { + final int[] disabledFeatures, IBiometricNativeHandle windowId) { checkPermission(MANAGE_BIOMETRIC); updateActiveGroup(userId, opPackageName); @@ -384,7 +386,7 @@ public class FaceService extends BiometricServiceBase { final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures, - ENROLL_TIMEOUT_SEC) { + ENROLL_TIMEOUT_SEC, windowId) { @Override public int[] getAcquireIgnorelist() { @@ -411,6 +413,14 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call + public void enrollRemotely(int userId, final IBinder token, final byte[] cryptoToken, + final IFaceServiceReceiver receiver, final String opPackageName, + final int[] disabledFeatures) { + checkPermission(MANAGE_BIOMETRIC); + // TODO(b/145027036): Implement this. + } + + @Override // Binder call public void cancelEnrollment(final IBinder token) { checkPermission(MANAGE_BIOMETRIC); cancelEnrollmentInternal(token); @@ -426,7 +436,7 @@ public class FaceService extends BiometricServiceBase { final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - 0 /* cookie */, false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */, null /* windowId */); authenticateInternal(client, opId, opPackageName); } @@ -442,7 +452,7 @@ public class FaceService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, - requireConfirmation); + requireConfirmation, null /* windowId */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -985,7 +995,8 @@ public class FaceService extends BiometricServiceBase { */ private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() { @Override - public int authenticate(long operationId, int groupId) throws RemoteException { + public int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "authenticate(): no face HAL!"); @@ -1026,7 +1037,7 @@ public class FaceService extends BiometricServiceBase { @Override public int enroll(byte[] cryptoToken, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException { + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no face HAL!"); @@ -1036,7 +1047,17 @@ public class FaceService extends BiometricServiceBase { for (int i = 0; i < cryptoToken.length; i++) { token.add(cryptoToken[i]); } - return daemon.enroll(token, timeout, disabledFeatures); + android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 = + android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom( + daemon); + if (daemon11 != null) { + return daemon11.enroll_1_1(token, timeout, disabledFeatures, windowId); + } else if (windowId == null) { + return daemon.enroll(token, timeout, disabledFeatures); + } else { + Slog.e(TAG, "enroll(): windowId is only supported in @1.1 HAL"); + return ERROR_ESRCH; + } } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java index 6150de151ccc..7a4e62e18474 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java @@ -38,7 +38,7 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId) throws RemoteException { mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver, - opPackageName, cookie, callingUid, callingPid, callingUserId); + opPackageName, cookie, callingUid, callingPid, callingUserId, null /* windowId */); } @Override 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 44797ad97b37..57d1867b3aca 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -37,6 +37,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; @@ -50,6 +51,7 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.os.SELinux; import android.os.SystemClock; @@ -132,9 +134,9 @@ public class FingerprintService extends BiometricServiceBase { DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, int cookie, - boolean requireConfirmation) { + boolean requireConfirmation, IBiometricNativeHandle windowId) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, cookie, requireConfirmation); + restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -198,7 +200,7 @@ public class FingerprintService extends BiometricServiceBase { @Override // Binder call public void enroll(final IBinder token, final byte[] cryptoToken, final int userId, final IFingerprintServiceReceiver receiver, final int flags, - final String opPackageName) { + final String opPackageName, IBiometricNativeHandle windowId) { checkPermission(MANAGE_FINGERPRINT); final boolean restricted = isRestricted(); @@ -206,7 +208,7 @@ public class FingerprintService extends BiometricServiceBase { final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */, - ENROLL_TIMEOUT_SEC) { + ENROLL_TIMEOUT_SEC, windowId) { @Override public boolean shouldVibrate() { return true; @@ -230,20 +232,22 @@ public class FingerprintService extends BiometricServiceBase { @Override // Binder call public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, - final String opPackageName) { + final String opPackageName, IBiometricNativeHandle windowId) { updateActiveGroup(groupId, opPackageName); final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, - 0 /* cookie */, false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */, + windowId); authenticateInternal(client, opId, opPackageName); } @Override // Binder call public void prepareForAuthentication(IBinder token, long opId, int groupId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, - int cookie, int callingUid, int callingPid, int callingUserId) { + int cookie, int callingUid, int callingPid, int callingUserId, + IBiometricNativeHandle windowId) { checkPermission(MANAGE_BIOMETRIC); updateActiveGroup(groupId, opPackageName); final boolean restricted = true; // BiometricPrompt is always restricted @@ -251,7 +255,8 @@ public class FingerprintService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, groupId, opId, restricted, opPackageName, cookie, - false /* requireConfirmation */); + false /* requireConfirmation */, + windowId); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -654,13 +659,24 @@ public class FingerprintService extends BiometricServiceBase { */ private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() { @Override - public int authenticate(long operationId, int groupId) throws RemoteException { + public int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "authenticate(): no fingerprint HAL!"); return ERROR_ESRCH; } - return daemon.authenticate(operationId, groupId); + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 = + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom( + daemon); + if (daemon22 != null) { + return daemon22.authenticate_2_2(operationId, groupId, windowId); + } else if (windowId == null) { + return daemon.authenticate(operationId, groupId); + } else { + Slog.e(TAG, "authenticate(): windowId is only supported in @2.2 HAL"); + return ERROR_ESRCH; + } } @Override @@ -695,13 +711,27 @@ public class FingerprintService extends BiometricServiceBase { @Override public int enroll(byte[] cryptoToken, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException { + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no fingerprint HAL!"); return ERROR_ESRCH; } - return daemon.enroll(cryptoToken, groupId, timeout); + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 = + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom( + daemon); + if (daemon22 != null) { + ArrayList<Byte> cryptoTokenAsList = new ArrayList<>(cryptoToken.length); + for (byte b : cryptoToken) { + cryptoTokenAsList.add(b); + } + return daemon22.enroll_2_2(cryptoTokenAsList, groupId, timeout, windowId); + } else if (windowId == null) { + return daemon.enroll(cryptoToken, groupId, timeout); + } else { + Slog.e(TAG, "enroll(): windowId is only supported in @2.2 HAL"); + return ERROR_ESRCH; + } } @Override diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 2fc9d04623f8..af474304c76e 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -16,6 +16,12 @@ package com.android.server.compat; +import static android.Manifest.permission.LOG_COMPAT_CHANGE; +import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -67,12 +73,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChange(long changeId, ApplicationInfo appInfo) { + checkCompatChangeLogPermission(); reportChange(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); } @Override public void reportChangeByPackageName(long changeId, String packageName, int userId) { + checkCompatChangeLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return; @@ -82,11 +90,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChangeByUid(long changeId, int uid) { + checkCompatChangeLogPermission(); reportChange(changeId, uid, ChangeReporter.STATE_LOGGED); } @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { + checkCompatChangeReadAndLogPermission(); if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, ChangeReporter.STATE_ENABLED); @@ -98,7 +108,9 @@ public class PlatformCompat extends IPlatformCompat.Stub { } @Override - public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + public boolean isChangeEnabledByPackageName(long changeId, String packageName, + @UserIdInt int userId) { + checkCompatChangeReadAndLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return true; @@ -108,6 +120,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabledByUid(long changeId, int uid) { + checkCompatChangeReadAndLogPermission(); String[] packages = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length == 0) { return true; @@ -140,6 +153,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverrides(CompatibilityChangeConfig overrides, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @@ -147,11 +161,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); } @Override public void clearOverrides(String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.removePackageOverrides(packageName); killPackage(packageName); } @@ -159,12 +175,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void clearOverridesForTest(String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.removePackageOverrides(packageName); } @Override public boolean clearOverride(long changeId, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); boolean existed = mCompatConfig.removeOverride(changeId, packageName); killPackage(packageName); return existed; @@ -172,11 +190,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { + checkCompatChangeReadAndLogPermission(); return mCompatConfig.getAppConfig(appInfo); } @Override public CompatibilityChangeInfo[] listAllChanges() { + checkCompatChangeReadPermission(); return mCompatConfig.dumpChanges(); } @@ -215,6 +235,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + checkCompatChangeReadAndLogPermission(); if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return; mCompatConfig.dumpConfig(pw); } @@ -272,4 +293,30 @@ public class PlatformCompat extends IPlatformCompat.Stub { Binder.restoreCallingIdentity(identity); } } + + private void checkCompatChangeLogPermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot log compat change usage"); + } + } + + private void checkCompatChangeReadPermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot read compat change"); + } + } + + private void checkCompatChangeOverridePermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot override compat change"); + } + } + + private void checkCompatChangeReadAndLogPermission() throws SecurityException { + checkCompatChangeReadPermission(); + checkCompatChangeLogPermission(); + } } diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java index 85dfbf411667..5d7af650db0b 100644 --- a/services/core/java/com/android/server/compat/PlatformCompatNative.java +++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java @@ -16,6 +16,8 @@ package com.android.server.compat; +import android.annotation.UserIdInt; + import com.android.internal.compat.IPlatformCompatNative; /** @@ -39,7 +41,8 @@ public class PlatformCompatNative extends IPlatformCompatNative.Stub { } @Override - public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + public boolean isChangeEnabledByPackageName(long changeId, String packageName, + @UserIdInt int userId) { return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName, userId); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 89af5b58ff72..cb88c4e8a739 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -49,6 +49,7 @@ import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.IpPrefix; +import android.net.IpSecManager; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; @@ -180,7 +181,10 @@ public class Vpn { private boolean mIsPackageTargetingAtLeastQ; private String mInterface; private Connection mConnection; - private LegacyVpnRunner mLegacyVpnRunner; + + /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */ + private VpnRunner mVpnRunner; + private PendingIntent mStatusIntent; private volatile boolean mEnableTeardown = true; private final INetworkManagementService mNetd; @@ -762,7 +766,7 @@ public class Vpn { mNetworkCapabilities.setUids(null); } - // Revoke the connection or stop LegacyVpnRunner. + // Revoke the connection or stop the VpnRunner. if (mConnection != null) { try { mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION, @@ -772,9 +776,9 @@ public class Vpn { } mContext.unbindService(mConnection); mConnection = null; - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + } else if (mVpnRunner != null) { + mVpnRunner.exit(); + mVpnRunner = null; } try { @@ -1510,8 +1514,8 @@ public class Vpn { @Override public void interfaceStatusChanged(String interfaze, boolean up) { synchronized (Vpn.this) { - if (!up && mLegacyVpnRunner != null) { - mLegacyVpnRunner.check(interfaze); + if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) { + ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze); } } } @@ -1528,9 +1532,10 @@ public class Vpn { mContext.unbindService(mConnection); mConnection = null; agentDisconnect(); - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + } else if (mVpnRunner != null) { + // agentDisconnect must be called from mVpnRunner.exit() + mVpnRunner.exit(); + mVpnRunner = null; } } } @@ -1913,23 +1918,40 @@ public class Vpn { private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { - stopLegacyVpnPrivileged(); + stopVpnRunnerPrivileged(); // Prepare for the new request. prepareInternal(VpnConfig.LEGACY_VPN); updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); - mLegacyVpnRunner.start(); + mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); + mVpnRunner.start(); + } + + /** + * Checks if this the currently running VPN (if any) was started by the Settings app + * + * <p>This includes both Legacy VPNs and Platform VPNs. + */ + private boolean isSettingsVpnLocked() { + return mVpnRunner != null && VpnConfig.LEGACY_VPN.equals(mPackage); } - /** Stop legacy VPN. Permissions must be checked by callers. */ - public synchronized void stopLegacyVpnPrivileged() { - if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + /** Stop VPN runner. Permissions must be checked by callers. */ + public synchronized void stopVpnRunnerPrivileged() { + if (!isSettingsVpnLocked()) { + return; + } + + final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; + mVpnRunner.exit(); + mVpnRunner = null; + + // LegacyVpn uses daemons that must be shut down before new ones are brought up. + // The same limitation does not apply to Platform VPNs. + if (isLegacyVpn) { synchronized (LegacyVpnRunner.TAG) { // wait for old thread to completely finish before spinning up // new instance, otherwise state updates can be out of order. @@ -1951,7 +1973,7 @@ public class Vpn { * Callers are responsible for checking permissions if needed. */ private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() { - if (mLegacyVpnRunner == null) return null; + if (!isSettingsVpnLocked()) return null; final LegacyVpnInfo info = new LegacyVpnInfo(); info.key = mConfig.user; @@ -1962,14 +1984,53 @@ public class Vpn { return info; } - public VpnConfig getLegacyVpnConfig() { - if (mLegacyVpnRunner != null) { + public synchronized VpnConfig getLegacyVpnConfig() { + if (isSettingsVpnLocked()) { return mConfig; } else { return null; } } + /** This class represents the common interface for all VPN runners. */ + private abstract class VpnRunner extends Thread { + + protected VpnRunner(String name) { + super(name); + } + + public abstract void run(); + + protected abstract void exit(); + } + + private class IkeV2VpnRunner extends VpnRunner { + private static final String TAG = "IkeV2VpnRunner"; + + private final IpSecManager mIpSecManager; + private final VpnProfile mProfile; + + IkeV2VpnRunner(VpnProfile profile) { + super(TAG); + mProfile = profile; + + // TODO: move this to startVpnRunnerPrivileged() + mConfig = new VpnConfig(); + mIpSecManager = mContext.getSystemService(IpSecManager.class); + } + + @Override + public void run() { + // TODO: Build IKE config, start IKE session + } + + @Override + public void exit() { + // TODO: Teardown IKE session & any resources. + agentDisconnect(); + } + } + /** * Bringing up a VPN connection takes time, and that is all this thread * does. Here we have plenty of time. The only thing we need to take @@ -1977,7 +2038,7 @@ public class Vpn { * requests will pile up. This could be done in a Handler as a state * machine, but it is much easier to read in the current form. */ - private class LegacyVpnRunner extends Thread { + private class LegacyVpnRunner extends VpnRunner { private static final String TAG = "LegacyVpnRunner"; private final String[] mDaemons; @@ -2047,13 +2108,21 @@ public class Vpn { mContext.registerReceiver(mBroadcastReceiver, filter); } - public void check(String interfaze) { + /** + * Checks if the parameter matches the underlying interface + * + * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has + * no ability to migrate between interfaces (or Networks). + */ + public void exitIfOuterInterfaceIs(String interfaze) { if (interfaze.equals(mOuterInterface)) { Log.i(TAG, "Legacy VPN is going down with " + interfaze); exit(); } } + /** Tears down this LegacyVpn connection */ + @Override public void exit() { // We assume that everything is reset after stopping the daemons. interrupt(); diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java index 6ba25a53248d..838dc84d4a0a 100644 --- a/services/core/java/com/android/server/display/DisplayAdapter.java +++ b/services/core/java/com/android/server/display/DisplayAdapter.java @@ -109,24 +109,14 @@ abstract class DisplayAdapter { */ protected final void sendDisplayDeviceEventLocked( final DisplayDevice device, final int event) { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayDeviceEvent(device, event); - } - }); + mHandler.post(() -> mListener.onDisplayDeviceEvent(device, event)); } /** * Sends a request to perform traversals. */ protected final void sendTraversalRequestLocked() { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onTraversalRequested(); - } - }); + mHandler.post(() -> mListener.onTraversalRequested()); } public static Display.Mode createMode(int width, int height, float refreshRate) { @@ -135,7 +125,7 @@ abstract class DisplayAdapter { } public interface Listener { - public void onDisplayDeviceEvent(DisplayDevice device, int event); - public void onTraversalRequested(); + void onDisplayDeviceEvent(DisplayDevice device, int event); + void onTraversalRequested(); } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 87a5730edc2e..fc9542a33a78 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * A display adapter for the local displays managed by Surface Flinger. @@ -64,8 +65,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; - private final LongSparseArray<LocalDisplayDevice> mDevices = - new LongSparseArray<LocalDisplayDevice>(); + private static final int NO_DISPLAY_MODE_ID = 0; + + private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>(); @SuppressWarnings("unused") // Becomes active at instantiation time. private PhysicalDisplayEventReceiver mPhysicalDisplayEventReceiver; @@ -118,21 +120,22 @@ final class LocalDisplayAdapter extends DisplayAdapter { physicalDisplayId); activeColorMode = Display.COLOR_MODE_INVALID; } - int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken); SurfaceControl.DesiredDisplayConfigSpecs configSpecs = SurfaceControl.getDesiredDisplayConfigSpecs(displayToken); + int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken); + Display.HdrCapabilities hdrCapabilities = + SurfaceControl.getHdrCapabilities(displayToken); LocalDisplayDevice device = mDevices.get(physicalDisplayId); if (device == null) { // Display was added. final boolean isInternal = mDevices.size() == 0; device = new LocalDisplayDevice(displayToken, physicalDisplayId, info, configs, activeConfig, configSpecs, colorModes, activeColorMode, - isInternal); + hdrCapabilities, isInternal); mDevices.put(physicalDisplayId, device); sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); - } else if (device.updateDisplayConfigsLocked(configs, activeConfig, configSpecs, - colorModes, activeColorMode)) { - // Display properties changed. + } else if (device.updateDisplayProperties(configs, activeConfig, + configSpecs, colorModes, activeColorMode, hdrCapabilities)) { sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); } } else { @@ -202,16 +205,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { LocalDisplayDevice(IBinder displayToken, long physicalDisplayId, SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs, int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs, - int[] colorModes, int activeColorMode, boolean isInternal) { + int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities, + boolean isInternal) { super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId); mPhysicalDisplayId = physicalDisplayId; mIsInternal = isInternal; mDisplayInfo = info; - - updateDisplayConfigsLocked(configs, activeConfigId, configSpecs, colorModes, - activeColorMode); - updateColorModesLocked(colorModes, activeColorMode); - + updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes, + activeColorMode, hdrCapabilities); mSidekickInternal = LocalServices.getService(SidekickInternal.class); if (mIsInternal) { LightsManager lights = LocalServices.getService(LightsManager.class); @@ -219,7 +220,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { } else { mBacklight = null; } - mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken); mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken); mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken); mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken); @@ -234,15 +234,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + /** + * Returns true if there is a change. + **/ + public boolean updateDisplayProperties(SurfaceControl.DisplayConfig[] configs, + int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs, + int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities) { + boolean changed = updateDisplayConfigsLocked(configs, activeConfigId, configSpecs); + changed |= updateColorModesLocked(colorModes, activeColorMode); + changed |= updateHdrCapabilitiesLocked(hdrCapabilities); + return changed; + } + public boolean updateDisplayConfigsLocked( SurfaceControl.DisplayConfig[] configs, int activeConfigId, - SurfaceControl.DesiredDisplayConfigSpecs configSpecs, int[] colorModes, - int activeColorMode) { + SurfaceControl.DesiredDisplayConfigSpecs configSpecs) { mDisplayConfigs = Arrays.copyOf(configs, configs.length); mActiveConfigId = activeConfigId; - // Build an updated list of all existing modes. - ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>(); + ArrayList<DisplayModeRecord> records = new ArrayList<>(); boolean modesAdded = false; for (int i = 0; i < configs.length; i++) { SurfaceControl.DisplayConfig config = configs[i]; @@ -282,7 +292,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed modes out from under us. // Schedule traversals to ensure that the correct state is reapplied if necessary. - if (mActiveModeId != 0 + if (mActiveModeId != NO_DISPLAY_MODE_ID && mActiveModeId != activeRecord.mMode.getModeId()) { mActiveModeInvalid = true; sendTraversalRequestLocked(); @@ -290,12 +300,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed display config specs out from // under us. If so, schedule a traversal to reapply our display config specs. - if (mDisplayModeSpecs.baseModeId != 0) { + if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { int activeBaseMode = findMatchingModeIdLocked(configSpecs.defaultConfig); // If we can't map the defaultConfig index to a mode, then the physical display // configs must have changed, and the code below for handling changes to the // list of available modes will take care of updating display config specs. - if (activeBaseMode != 0) { + if (activeBaseMode != NO_DISPLAY_MODE_ID) { if (mDisplayModeSpecs.baseModeId != activeBaseMode || mDisplayModeSpecs.refreshRateRange.min != configSpecs.minRefreshRate || mDisplayModeSpecs.refreshRateRange.max @@ -319,18 +329,23 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSupportedModes.put(record.mMode.getModeId(), record); } - // Update the default mode, if needed. - if (findDisplayConfigIdLocked(mDefaultModeId) < 0) { - if (mDefaultModeId != 0) { - Slog.w(TAG, "Default display mode no longer available, using currently" - + " active mode as default."); - } + // For a new display, we need to initialize the default mode ID. + if (mDefaultModeId == NO_DISPLAY_MODE_ID) { + mDefaultModeId = activeRecord.mMode.getModeId(); + } else if (modesAdded && mActiveModeId != activeRecord.mMode.getModeId()) { + Slog.d(TAG, "New display modes are added and the active mode has changed, " + + "use active mode as default mode."); + mActiveModeId = activeRecord.mMode.getModeId(); + mDefaultModeId = activeRecord.mMode.getModeId(); + } else if (findDisplayConfigIdLocked(mDefaultModeId) < 0) { + Slog.w(TAG, "Default display mode no longer available, using currently" + + " active mode as default."); mDefaultModeId = activeRecord.mMode.getModeId(); } // Determine whether the display mode specs' base mode is still there. if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) { - if (mDisplayModeSpecs.baseModeId != 0) { + if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { Slog.w(TAG, "DisplayModeSpecs base mode no longer available, using currently" + " active mode."); @@ -341,7 +356,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Determine whether the active mode is still there. if (mSupportedModes.indexOfKey(mActiveModeId) < 0) { - if (mActiveModeId != 0) { + if (mActiveModeId != NO_DISPLAY_MODE_ID) { Slog.w(TAG, "Active display mode no longer available, reverting to default" + " mode."); } @@ -389,14 +404,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSystemBrightnessToNits = sysToNits; } - private boolean updateColorModesLocked(int[] colorModes, - int activeColorMode) { - List<Integer> pendingColorModes = new ArrayList<>(); + private boolean updateColorModesLocked(int[] colorModes, int activeColorMode) { + if (colorModes == null) { + return false; + } - if (colorModes == null) return false; + List<Integer> pendingColorModes = new ArrayList<>(); // Build an updated list of all existing color modes. boolean colorModesAdded = false; - for (int colorMode: colorModes) { + for (int colorMode : colorModes) { if (!mSupportedColorModes.contains(colorMode)) { colorModesAdded = true; } @@ -441,6 +457,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + private boolean updateHdrCapabilitiesLocked(Display.HdrCapabilities newHdrCapabilities) { + // If the HDR capabilities haven't changed, then we're done here. + if (Objects.equals(mHdrCapabilities, newHdrCapabilities)) { + return false; + } + mHdrCapabilities = newHdrCapabilities; + return true; + } + private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayConfig config) { for (int i = 0; i < mSupportedModes.size(); i++) { DisplayModeRecord record = mSupportedModes.valueAt(i); @@ -783,7 +808,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } mActiveConfigId = activeConfigId; mActiveModeId = findMatchingModeIdLocked(activeConfigId); - mActiveModeInvalid = mActiveModeId == 0; + mActiveModeInvalid = mActiveModeId == NO_DISPLAY_MODE_ID; if (mActiveModeInvalid) { Slog.w(TAG, "In unknown mode after setting allowed configs" + ", activeConfigId=" + mActiveConfigId); @@ -908,7 +933,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { return record.mMode.getModeId(); } } - return 0; + return NO_DISPLAY_MODE_ID; } private void updateDeviceInfoLocked() { diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java index 5161a77e4ede..b0e2e6432c77 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -113,11 +113,27 @@ public final class IncrementalManagerShellCommand extends ShellCommand { return ERROR_COMMAND_EXECUTION; } - final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs(); - if (dataLoaderDynamicArgs == null) { + final Map<String, ParcelFileDescriptor> fds = getShellFileDescriptors(); + if (fds == null) { pw.println("File names and sizes don't match."); return ERROR_DATA_LOADER_INIT; } + // dup FDs before closing them + final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>(); + for (Map.Entry<String, ParcelFileDescriptor> nfd : fds.entrySet()) { + try { + dataLoaderDynamicArgs.put(nfd.getKey(), nfd.getValue().dup()); + } catch (IOException ignored) { + pw.println("Failed to dup shell file descriptor"); + return ERROR_DATA_LOADER_INIT; + } finally { + try { + nfd.getValue().close(); + } catch (IOException ignored) { + } + } + } + final DataLoaderParams params = DataLoaderParams.forIncremental( new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "", dataLoaderDynamicArgs); @@ -131,17 +147,9 @@ public final class IncrementalManagerShellCommand extends ShellCommand { try { int sessionId = packageInstaller.createSession(sessionParams); pw.println("Successfully opened session: sessionId = " + sessionId); - } catch (Exception ex) { + } catch (IOException ex) { pw.println("Failed to create session."); return ERROR_COMMAND_EXECUTION; - } finally { - try { - for (Map.Entry<String, ParcelFileDescriptor> nfd - : dataLoaderDynamicArgs.entrySet()) { - nfd.getValue().close(); - } - } catch (IOException ignored) { - } } return 0; } @@ -177,7 +185,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { 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(location, file.getName(), file.getSize(), file.getMetadata(), + null); } session.commit(localReceiver.getIntentSender()); final Intent result = localReceiver.getResult(); @@ -212,7 +221,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public void send(int code, Intent intent, String resolvedType, + IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { try { @@ -237,14 +247,14 @@ public final class IncrementalManagerShellCommand extends ShellCommand { } /** Helpers. */ - private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() { - Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>(); + private Map<String, ParcelFileDescriptor> getShellFileDescriptors() { + Map<String, ParcelFileDescriptor> fds = new HashMap<>(); final FileDescriptor outFd = getOutFileDescriptor(); final FileDescriptor inFd = getInFileDescriptor(); try { - dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd)); - dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd)); - return dataLoaderDynamicArgs; + fds.put("inFd", ParcelFileDescriptor.dup(inFd)); + fds.put("outFd", ParcelFileDescriptor.dup(outFd)); + return fds; } catch (Exception ex) { Slog.e(TAG, "Failed to dup FDs"); return null; @@ -292,7 +302,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { pw.println("Invalid file index in: " + fileArgs); return null; } - final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8); + final byte[] metadata = String.valueOf(index).getBytes( + StandardCharsets.UTF_8); fileList.add(new InstallationFile(name, size, metadata)); break; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 90358ca1f133..a8f706cdd629 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -223,7 +223,7 @@ public class InputManagerService extends IInputManager.Stub int displayId, InputApplicationHandle application); private static native void nativeSetFocusedDisplay(long ptr, int displayId); private static native boolean nativeTransferTouchFocus(long ptr, - InputChannel fromChannel, InputChannel toChannel); + IBinder fromChannelToken, IBinder toChannelToken); private static native void nativeSetPointerSpeed(long ptr, int speed); private static native void nativeSetShowTouches(long ptr, boolean enabled); private static native void nativeSetInteractive(long ptr, boolean interactive); @@ -1554,14 +1554,29 @@ public class InputManagerService extends IInputManager.Stub * @return True if the transfer was successful. False if the window with the * specified channel did not actually have touch focus at the time of the request. */ - public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { - if (fromChannel == null) { - throw new IllegalArgumentException("fromChannel must not be null."); - } - if (toChannel == null) { - throw new IllegalArgumentException("toChannel must not be null."); - } - return nativeTransferTouchFocus(mPtr, fromChannel, toChannel); + public boolean transferTouchFocus(@NonNull InputChannel fromChannel, + @NonNull InputChannel toChannel) { + return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken()); + } + + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannelToken The channel token of a window that currently has touch focus. + * @param toChannelToken The channel token of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(@NonNull IBinder fromChannelToken, + @NonNull IBinder toChannelToken) { + Objects.nonNull(fromChannelToken); + Objects.nonNull(toChannelToken); + return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken); } @Override // Binder call diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 9754b6d4db02..0450647b9403 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -39,13 +39,20 @@ import android.content.integrity.Rule; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageParser; +import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; import android.content.pm.Signature; +import android.content.pm.SigningInfo; +import android.content.pm.parsing.ApkParseUtils; +import android.content.pm.parsing.PackageInfoUtils; +import android.content.pm.parsing.ParsedPackage; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; @@ -67,6 +74,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -121,11 +129,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { IntegrityFileManager.getInstance(), handlerThread.getThreadHandler(), Settings.Global.getInt( - context.getContentResolver(), - Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - 0) - == 1 - ); + context.getContentResolver(), + Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, + 0) + == 1 + ); } @VisibleForTesting @@ -260,16 +268,25 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return; } - String appCert = getCertificateFingerprint(packageInfo); + List<String> appCertificates = getCertificateFingerprint(packageInfo); + List<String> installerCertificates = + getInstallerCertificateFingerprint(installerPackageName); + + // TODO (b/148373316): Figure out what field contains which fields are populated for + // rotated and the multiple signers. Until then, return the first certificate. + String appCert = appCertificates.isEmpty() ? "" : appCertificates.get(0); + String installerCert = + installerCertificates.isEmpty() ? "" : installerCertificates.get(0); + + Slog.w(TAG, appCertificates.toString()); AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); builder.setPackageName(getPackageNameNormalized(packageName)); - builder.setAppCertificate(appCert == null ? "" : appCert); + builder.setAppCertificate(appCert); builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); builder.setInstallerName(getPackageNameNormalized(installerPackageName)); - builder.setInstallerCertificate( - getInstallerCertificateFingerprint(installerPackageName)); + builder.setInstallerCertificate(installerCert); builder.setIsPreInstalled(isSystemApp(packageName)); AppInstallMetadata appInstallMetadata = builder.build(); @@ -320,7 +337,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { * Verify the UID and return the installer package name. * * @return the package name of the installer, or null if it cannot be determined or it is - * installed via adb. + * installed via adb. */ @Nullable private String getInstallerPackageName(Intent intent) { @@ -399,23 +416,27 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) { - return getFingerprint(getSignature(packageInfo)); - } - - private String getInstallerCertificateFingerprint(String installer) { + private List<String> getInstallerCertificateFingerprint(String installer) { if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) { - return INSTALLER_CERT_NOT_APPLICABLE; + return Collections.emptyList(); } try { PackageInfo installerInfo = mContext.getPackageManager() - .getPackageInfo(installer, PackageManager.GET_SIGNATURES); + .getPackageInfo(installer, PackageManager.GET_SIGNING_CERTIFICATES); return getCertificateFingerprint(installerInfo); } catch (PackageManager.NameNotFoundException e) { Slog.i(TAG, "Installer package " + installer + " not found."); - return ""; + return Collections.emptyList(); + } + } + + private List<String> getCertificateFingerprint(@NonNull PackageInfo packageInfo) { + ArrayList<String> certificateFingerprints = new ArrayList(); + for (Signature signature : getSignatures(packageInfo)) { + certificateFingerprints.add(getFingerprint(signature)); } + return certificateFingerprints; } /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */ @@ -445,12 +466,15 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return packageCertMap; } - private static Signature getSignature(@NonNull PackageInfo packageInfo) { - if (packageInfo.signatures == null || packageInfo.signatures.length < 1) { + private static Signature[] getSignatures(@NonNull PackageInfo packageInfo) { + SigningInfo signingInfo = packageInfo.signingInfo; + + if (signingInfo == null || signingInfo.getApkContentsSigners().length < 1) { throw new IllegalArgumentException("Package signature not found in " + packageInfo); } - // Only the first element is guaranteed to be present. - return packageInfo.signatures[0]; + + // We are only interested in evaluating the active signatures. + return signingInfo.getApkContentsSigners(); } private static String getFingerprint(Signature cert) { @@ -489,20 +513,14 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } - PackageInfo packageInfo; + + PackageParser parser = new PackageParser(); try { - // The installation path will be a directory for a multi-apk install on L+ - if (installationPath.isDirectory()) { - packageInfo = getMultiApkInfo(installationPath); - } else { - packageInfo = - mContext.getPackageManager() - .getPackageArchiveInfo( - installationPath.getPath(), - PackageManager.GET_SIGNATURES - | PackageManager.GET_META_DATA); - } - return packageInfo; + ParsedPackage pkg = parser.parseParsedPackage(installationPath, 0, false); + int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA; + ApkParseUtils.collectCertificates(pkg, false); + return PackageInfoUtils.generate(pkg, null, flags, 0, 0, null, new PackageUserState(), + UserHandle.getCallingUserId()); } catch (Exception e) { throw new IllegalArgumentException("Exception reading " + dataUri, e); } @@ -515,7 +533,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { mContext.getPackageManager() .getPackageArchiveInfo( baseFile.getAbsolutePath(), - PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA); + PackageManager.GET_SIGNING_CERTIFICATES + | PackageManager.GET_META_DATA); if (basePackageInfo == null) { for (File apkFile : multiApkDirectory.listFiles()) { diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 5123362d84ba..83588846ade8 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.media.MediaRoute2ProviderInfo; +import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -54,6 +55,7 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession(String packageName, String routeId, long requestId, @Nullable Bundle sessionHints); public abstract void releaseSession(String sessionId); + public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference); public abstract void selectRoute(String sessionId, String routeId); public abstract void deselectRoute(String sessionId, String routeId); @@ -61,7 +63,6 @@ abstract class MediaRoute2Provider { public abstract void sendControlRequest(String routeId, Intent request); public abstract void requestSetVolume(String routeId, int volume); - public abstract void requestUpdateVolume(String routeId, int delta); @NonNull public String getUniqueId() { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index e23542edb214..d08fb71d3dee 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -25,6 +25,7 @@ import android.media.IMediaRoute2Provider; import android.media.IMediaRoute2ProviderClient; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.Handler; @@ -93,6 +94,14 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override + public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + if (mConnectionReady) { + mActiveConnection.updateDiscoveryPreference(discoveryPreference); + updateBinding(); + } + } + + @Override public void selectRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.selectRoute(sessionId, routeId); @@ -129,14 +138,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - @Override - public void requestUpdateVolume(String routeId, int delta) { - if (mConnectionReady) { - mActiveConnection.requestUpdateVolume(routeId, delta); - updateBinding(); - } - } - public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -461,6 +462,14 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } + public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + try { + mProvider.updateDiscoveryPreference(discoveryPreference); + } catch (RemoteException ex) { + Slog.e(TAG, "updateDiscoveryPreference(): Failed to deliver request."); + } + } + public void selectRoute(String sessionId, String routeId) { try { mProvider.selectRoute(sessionId, routeId); @@ -501,14 +510,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void requestUpdateVolume(String routeId, int delta) { - try { - mProvider.requestUpdateVolume(routeId, delta); - } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to request update volume.", ex); - } - } - @Override public void binderDied() { mHandler.post(() -> onConnectionDied(Connection.this)); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 9594659f23d0..b1133223e59a 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -334,20 +334,6 @@ class MediaRouter2ServiceImpl { } } - public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) { - Objects.requireNonNull(client, "client must not be null"); - Objects.requireNonNull(route, "route must not be null"); - - final long token = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - requestUpdateVolumeLocked(client, route, delta); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - public void requestCreateClientSession(IMediaRouter2Manager manager, String packageName, MediaRoute2Info route, int requestId) { final long token = Binder.clearCallingIdentity(); @@ -375,21 +361,6 @@ class MediaRouter2ServiceImpl { } } - public void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - MediaRoute2Info route, int delta) { - Objects.requireNonNull(manager, "manager must not be null"); - Objects.requireNonNull(route, "route must not be null"); - - final long token = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - requestUpdateVolumeLocked(manager, route, delta); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - @NonNull public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { final long token = Binder.clearCallingIdentity(); @@ -598,6 +569,9 @@ class MediaRouter2ServiceImpl { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); + clientRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::updateDiscoveryPreference, + clientRecord.mUserRecord.mHandler)); } } @@ -625,18 +599,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route, - int delta) { - final IBinder binder = client.asBinder(); - Client2Record clientRecord = mAllClientRecords.get(binder); - - if (clientRecord != null) { - clientRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestUpdateVolume, - clientRecord.mUserRecord.mHandler, route, delta)); - } - } - private void registerManagerLocked(IMediaRouter2Manager manager, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = manager.asBinder(); @@ -710,18 +672,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route, - int delta) { - final IBinder binder = manager.asBinder(); - ManagerRecord managerRecord = mAllManagerRecords.get(binder); - - if (managerRecord != null) { - managerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestUpdateVolume, - managerRecord.mUserRecord.mHandler, route, delta)); - } - } - private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -856,6 +806,7 @@ class MediaRouter2ServiceImpl { //TODO: make records private for thread-safety final ArrayList<Client2Record> mClientRecords = new ArrayList<>(); final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>(); + RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY; final UserHandler mHandler; UserRecord(int userId) { @@ -885,8 +836,6 @@ class MediaRouter2ServiceImpl { public final int mClientId; public RouteDiscoveryPreference mDiscoveryPreference; - public boolean mIsManagerSelecting; - public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; Client2Record(UserRecord userRecord, IMediaRouter2Client client, @@ -1003,6 +952,7 @@ class MediaRouter2ServiceImpl { public void onAddProvider(MediaRoute2ProviderProxy provider) { provider.setCallback(this); mMediaProviders.add(provider); + provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); } @Override @@ -1456,13 +1406,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolume(MediaRoute2Info route, int delta) { - final MediaRoute2Provider provider = findProvider(route.getProviderId()); - if (provider != null) { - provider.requestUpdateVolume(route.getOriginalId(), delta); - } - } - private List<IMediaRouter2Client> getClients() { final List<IMediaRouter2Client> clients = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); @@ -1642,6 +1585,25 @@ class MediaRouter2ServiceImpl { } } + private void updateDiscoveryPreference() { + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return; + } + List<RouteDiscoveryPreference> discoveryPreferences = new ArrayList<>(); + synchronized (service.mLock) { + for (Client2Record clientRecord : mUserRecord.mClientRecords) { + discoveryPreferences.add(clientRecord.mDiscoveryPreference); + } + mUserRecord.mCompositeDiscoveryPreference = + new RouteDiscoveryPreference.Builder(discoveryPreferences) + .build(); + } + for (MediaRoute2Provider provider : mMediaProviders) { + provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); + } + } + private MediaRoute2Provider findProvider(String providerId) { for (MediaRoute2Provider provider : mMediaProviders) { if (TextUtils.equals(provider.getUniqueId(), providerId)) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index e1d38039b955..57f0328f1c00 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -539,12 +539,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) { - mService2.requestUpdateVolume2(client, route, delta); - } - - // Binder call - @Override public void requestSetVolume2Manager(IMediaRouter2Manager manager, MediaRoute2Info route, int volume) { mService2.requestSetVolume2Manager(manager, route, volume); @@ -552,13 +546,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - MediaRoute2Info route, int delta) { - mService2.requestUpdateVolume2Manager(manager, route, delta); - } - - // Binder call - @Override public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { return mService2.getActiveSessions(manager); } diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 820731d20c00..11b7a8a23e14 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -52,6 +52,8 @@ public class MediaSession2Record implements MediaSessionRecordImpl { private boolean mIsConnected; @GuardedBy("mLock") private int mPolicies; + @GuardedBy("mLock") + private boolean mIsClosed; public MediaSession2Record(Session2Token sessionToken, MediaSessionService service, Looper handlerLooper, int policies) { @@ -119,6 +121,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl { @Override public void close() { synchronized (mLock) { + mIsClosed = true; // Call close regardless of the mIsAvailable. This may be called when it's not yet // connected. mController.close(); @@ -126,6 +129,13 @@ public class MediaSession2Record implements MediaSessionRecordImpl { } @Override + public boolean isClosed() { + synchronized (mLock) { + return mIsClosed; + } + } + + @Override public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb) { // TODO(jaewan): Implement. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 05f7e1d7c3a5..7bcbcd4a3d09 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -420,6 +420,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } } + @Override + public boolean isClosed() { + synchronized (mLock) { + return mDestroyed; + } + } + /** * Sends media button. * diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 6e1088088ced..74e6dedaeee5 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -154,4 +154,9 @@ public interface MediaSessionRecordImpl extends AutoCloseable { */ @Override void close(); + + /** + * Returns whether {@link #close()} is called before. + */ + boolean isClosed(); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index d0efef041180..88b884efce83 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -434,9 +434,14 @@ public class MediaSessionService extends SystemService implements Monitor { if (DEBUG) { Log.d(TAG, "Destroying " + session); } + if (session.isClosed()) { + Log.w(TAG, "Destroying already destroyed session. Ignoring."); + return; + } + FullUserRecord user = getFullUserRecordLocked(session.getUserId()); - if (user != null) { + if (user != null && session instanceof MediaSessionRecord) { final int uid = session.getUid(); final int sessionCount = user.mUidToSessionCount.get(uid, 0); if (sessionCount <= 0) { @@ -570,6 +575,13 @@ public class MediaSessionService extends SystemService implements Monitor { throw new RuntimeException("Session request from invalid user."); } + final int sessionCount = user.mUidToSessionCount.get(callerUid, 0); + if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID + && !hasMediaControlPermission(callerPid, callerUid)) { + throw new RuntimeException("Created too many sessions. count=" + + sessionCount + ")"); + } + final MediaSessionRecord session; try { session = new MediaSessionRecord(callerPid, callerUid, userId, @@ -579,12 +591,6 @@ public class MediaSessionService extends SystemService implements Monitor { throw new RuntimeException("Media Session owner died prematurely.", e); } - final int sessionCount = user.mUidToSessionCount.get(callerUid, 0); - if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID - && !hasMediaControlPermission(callerPid, callerUid)) { - throw new RuntimeException("Created too many sessions. count=" - + sessionCount + ")"); - } user.mUidToSessionCount.put(callerUid, sessionCount + 1); user.mPriorityStack.addSession(session); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 924a9b71c8ec..888f7ce74ba4 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -29,6 +29,7 @@ import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.Handler; @@ -118,6 +119,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void releaseSession(String sessionId) { // Do nothing } + @Override + public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + // Do nothing + } @Override public void selectRoute(String sessionId, String routeId) { @@ -148,11 +153,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void requestSetVolume(String routeId, int volume) { } - //TODO: implement method - @Override - public void requestUpdateVolume(String routeId, int delta) { - } - private void initializeDefaultRoute() { mDefaultRoute = new MediaRoute2Info.Builder( DEFAULT_ROUTE_ID, diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index ef8f6479cb3d..3cafafffc62a 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -138,7 +138,7 @@ public class LockdownVpnTracker { if (egressDisconnected || egressChanged) { mAcceptedEgressIface = null; - mVpn.stopLegacyVpnPrivileged(); + mVpn.stopVpnRunnerPrivileged(); } if (egressDisconnected) { hideNotification(); @@ -218,7 +218,7 @@ public class LockdownVpnTracker { mAcceptedEgressIface = null; mErrorCount = 0; - mVpn.stopLegacyVpnPrivileged(); + mVpn.stopVpnRunnerPrivileged(); mVpn.setLockdown(false); hideNotification(); diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java index d91d5410d004..af8baa50d501 100644 --- a/services/core/java/com/android/server/notification/BadgeExtractor.java +++ b/services/core/java/com/android/server/notification/BadgeExtractor.java @@ -43,9 +43,9 @@ public class BadgeExtractor implements NotificationSignalExtractor { if (DBG) Slog.d(TAG, "missing config"); return null; } - boolean userWantsBadges = mConfig.badgingEnabled(record.sbn.getUser()); + boolean userWantsBadges = mConfig.badgingEnabled(record.getSbn().getUser()); boolean appCanShowBadge = - mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid()); + mConfig.canShowBadge(record.getSbn().getPackageName(), record.getSbn().getUid()); if (!userWantsBadges || !appCanShowBadge) { record.setShowBadge(false); } else { diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index e59bf1642ba8..c9c8042685d1 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -42,7 +42,7 @@ public class BubbleExtractor implements NotificationSignalExtractor { return null; } boolean appCanShowBubble = - mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid()); + mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid()); if (!mConfig.bubblesEnabled() || !appCanShowBubble) { record.setAllowBubble(false); } else { diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index 0e14364b4280..83ca69956033 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -15,10 +15,8 @@ */ package com.android.server.notification; -import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; -import android.util.FeatureFlagUtils; import android.util.Slog; /** @@ -47,9 +45,9 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor return null; } NotificationChannel updatedChannel = mConfig.getConversationNotificationChannel( - record.sbn.getPackageName(), - record.sbn.getUid(), record.getChannel().getId(), - record.sbn.getShortcutId(mContext), true, false); + record.getSbn().getPackageName(), + record.getSbn().getUid(), record.getChannel().getId(), + record.getSbn().getShortcutId(mContext), true, false); record.updateNotificationChannel(updatedChannel); return null; diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index a7e40cbc5b66..29b5e81447f8 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -122,8 +122,8 @@ public class NotificationComparator return -1 * Integer.compare(leftPackagePriority, rightPackagePriority); } - final int leftPriority = left.sbn.getNotification().priority; - final int rightPriority = right.sbn.getNotification().priority; + final int leftPriority = left.getSbn().getNotification().priority; + final int rightPriority = right.getSbn().getNotification().priority; if (leftPriority != rightPriority) { // by priority, high to low return -1 * Integer.compare(leftPriority, rightPriority); @@ -169,7 +169,7 @@ public class NotificationComparator } protected boolean isImportantMessaging(NotificationRecord record) { - return mMessagingUtil.isImportantMessaging(record.sbn, record.getImportance()); + return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance()); } private boolean isOngoing(NotificationRecord record) { @@ -183,7 +183,7 @@ public class NotificationComparator private boolean isCall(NotificationRecord record) { return record.isCategory(Notification.CATEGORY_CALL) - && isDefaultPhoneApp(record.sbn.getPackageName()); + && isDefaultPhoneApp(record.getSbn().getPackageName()); } private boolean isDefaultPhoneApp(String pkg) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 68cc0141e657..f07113591fa5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -684,14 +684,14 @@ public class NotificationManagerService extends SystemService { if (summary == null) { return; } - int oldFlags = summary.sbn.getNotification().flags; + int oldFlags = summary.getSbn().getNotification().flags; if (needsOngoingFlag) { - summary.sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT; } else { - summary.sbn.getNotification().flags &= ~FLAG_ONGOING_EVENT; + summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT; } - if (summary.sbn.getNotification().flags != oldFlags) { + if (summary.getSbn().getNotification().flags != oldFlags) { mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground)); } } @@ -917,7 +917,7 @@ public class NotificationManagerService extends SystemService { r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), nv.rank, nv.count); - StatusBarNotification sbn = r.sbn; + StatusBarNotification sbn = r.getSbn(); cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(), sbn.getId(), Notification.FLAG_AUTO_CANCEL, FLAG_FOREGROUND_SERVICE, false, r.getUserId(), @@ -959,7 +959,7 @@ public class NotificationManagerService extends SystemService { nv.recycle(); reportUserInteraction(r); mAssistants.notifyAssistantActionClicked( - r.sbn, actionIndex, action, generatedByAssistant); + r.getSbn(), actionIndex, action, generatedByAssistant); } } @@ -1044,7 +1044,7 @@ public class NotificationManagerService extends SystemService { reportSeen(r); } r.setVisibility(true, nv.rank, nv.count); - mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true); + mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); // hasBeenVisiblyExpanded must be called after updating the expansion state of @@ -1063,7 +1063,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; r.setVisibility(false, nv.rank, nv.count); - mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false); + mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false); nv.recycle(); } } @@ -1090,7 +1090,8 @@ public class NotificationManagerService extends SystemService { r.recordExpanded(); reportUserInteraction(r); } - mAssistants.notifyAssistantExpansionChangedLocked(r.sbn, userAction, expanded); + mAssistants.notifyAssistantExpansionChangedLocked( + r.getSbn(), userAction, expanded); } } } @@ -1106,7 +1107,7 @@ public class NotificationManagerService extends SystemService { .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION) .setType(MetricsEvent.TYPE_ACTION)); reportUserInteraction(r); - mAssistants.notifyAssistantNotificationDirectReplyLocked(r.sbn); + mAssistants.notifyAssistantNotificationDirectReplyLocked(r.getSbn()); } } } @@ -1150,7 +1151,7 @@ public class NotificationManagerService extends SystemService { // Treat clicking on a smart reply as a user interaction. reportUserInteraction(r); mAssistants.notifyAssistantSuggestedReplySent( - r.sbn, reply, r.getSuggestionsGeneratedByAssistant()); + r.getSbn(), reply, r.getSuggestionsGeneratedByAssistant()); } } } @@ -1170,7 +1171,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { - final StatusBarNotification n = r.sbn; + final StatusBarNotification n = r.getSbn(); final int callingUid = n.getUid(); final String pkg = n.getPackageName(); applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isBubble); @@ -1372,9 +1373,9 @@ public class NotificationManagerService extends SystemService { record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY)); } if (record != null) { - cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(), - record.sbn.getPackageName(), record.sbn.getTag(), - record.sbn.getId(), 0, + cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(), + record.getSbn().getPackageName(), record.getSbn().getTag(), + record.getSbn().getId(), 0, FLAG_FOREGROUND_SERVICE, true, record.getUserId(), REASON_TIMEOUT, null); } @@ -1637,7 +1638,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(bubbleKey); if (r != null) { - final StatusBarNotification n = r.sbn; + final StatusBarNotification n = r.getSbn(); final int callingUid = n.getUid(); final String pkg = n.getPackageName(); applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isAppForeground); @@ -1780,7 +1781,7 @@ public class NotificationManagerService extends SystemService { if (mNotificationsByKey.containsKey(posted.getKey())) { count--; } - if (posted.sbn.isGroup() && posted.getNotification().isGroupSummary()) { + if (posted.getSbn().isGroup() && posted.getNotification().isGroupSummary()) { count--; } } @@ -1802,8 +1803,8 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting void addNotification(NotificationRecord r) { mNotificationList.add(r); - mNotificationsByKey.put(r.sbn.getKey(), r); - if (r.sbn.isGroup()) { + mNotificationsByKey.put(r.getSbn().getKey(), r); + if (r.getSbn().isGroup()) { mSummaryByGroupKey.put(r.getGroupKey(), r); } } @@ -2059,9 +2060,9 @@ public class NotificationManagerService extends SystemService { if (DBG) { Slog.d(TAG, "Reposting " + r.getKey()); } - enqueueNotificationInternal(r.sbn.getPackageName(), r.sbn.getOpPkg(), - r.sbn.getUid(), r.sbn.getInitialPid(), r.sbn.getTag(), r.sbn.getId(), - r.sbn.getNotification(), userId); + enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), + r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), + r.getSbn().getId(), r.getSbn().getNotification(), userId); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } @@ -2201,7 +2202,7 @@ public class NotificationManagerService extends SystemService { String pkg; synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); - pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null; + pkg = r != null && r.getSbn() != null ? r.getSbn().getPackageName() : null; } boolean isAppForeground = pkg != null && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; @@ -2209,7 +2210,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) return; updateAutobundledSummaryFlags(r.getUser().getIdentifier(), - r.sbn.getPackageName(), needsOngoingFlag, isAppForeground); + r.getSbn().getPackageName(), needsOngoingFlag, isAppForeground); } } }); @@ -2280,7 +2281,8 @@ public class NotificationManagerService extends SystemService { final long updatedSuppressedEffects = calculateSuppressedEffects(); if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return; final List<ComponentName> suppressors = getSuppressors(); - ZenLog.traceEffectsSuppressorChanged(mEffectsSuppressors, suppressors, updatedSuppressedEffects); + ZenLog.traceEffectsSuppressorChanged( + mEffectsSuppressors, suppressors, updatedSuppressedEffects); mEffectsSuppressors = suppressors; mZenModeHelper.setSuppressedEffects(updatedSuppressedEffects); sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); @@ -2486,6 +2488,18 @@ public class NotificationManagerService extends SystemService { scheduleInterruptionFilterChanged(interruptionFilter); } + int correctCategory(int requestedCategoryList, int categoryType, + int currentCategoryList) { + if ((requestedCategoryList & categoryType) != 0 + && (currentCategoryList & categoryType) == 0) { + requestedCategoryList &= ~categoryType; + } else if ((requestedCategoryList & categoryType) == 0 + && (currentCategoryList & categoryType) != 0){ + requestedCategoryList |= categoryType; + } + return requestedCategoryList; + } + @VisibleForTesting INotificationManager getBinderService() { return INotificationManager.Stub.asInterface(mService); @@ -2498,8 +2512,8 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") protected void reportSeen(NotificationRecord r) { if (!r.isProxied()) { - mAppUsageStats.reportEvent(r.sbn.getPackageName(), - getRealUserId(r.sbn.getUserId()), + mAppUsageStats.reportEvent(r.getSbn().getPackageName(), + getRealUserId(r.getSbn().getUserId()), UsageEvents.Event.NOTIFICATION_SEEN); } } @@ -2574,18 +2588,18 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") protected void maybeRecordInterruptionLocked(NotificationRecord r) { if (r.isInterruptive() && !r.hasRecordedInterruption()) { - mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(), + mAppUsageStats.reportInterruptiveNotification(r.getSbn().getPackageName(), r.getChannel().getId(), - getRealUserId(r.sbn.getUserId())); + getRealUserId(r.getSbn().getUserId())); mHistoryManager.addNotification(new HistoricalNotification.Builder() - .setPackage(r.sbn.getPackageName()) - .setUid(r.sbn.getUid()) + .setPackage(r.getSbn().getPackageName()) + .setUid(r.getSbn().getUid()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) .setPostedTimeMs(System.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( - r.sbn.getPackageContext(getContext()), r.getNotification())) + r.getSbn().getPackageContext(getContext()), r.getNotification())) .setIcon(r.getNotification().getSmallIcon()) .build()); r.setRecordedInterruption(true); @@ -2632,8 +2646,8 @@ public class NotificationManagerService extends SystemService { * @param r notification record */ protected void reportUserInteraction(NotificationRecord r) { - mAppUsageStats.reportEvent(r.sbn.getPackageName(), - getRealUserId(r.sbn.getUserId()), + mAppUsageStats.reportEvent(r.getSbn().getPackageName(), + getRealUserId(r.getSbn().getUserId()), UsageEvents.Event.USER_INTERACTION); } @@ -2667,18 +2681,24 @@ public class NotificationManagerService extends SystemService { @Override public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @Nullable ITransientNotificationCallback callback) { - enqueueToast(pkg, token, text, null, duration, displayId, callback); + enqueueToast(pkg, token, text, null, duration, displayId, callback, false); } @Override public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId) { - enqueueToast(pkg, token, null, callback, duration, displayId, null); + enqueueToast(pkg, token, null, callback, duration, displayId, null, true); + } + + @Override + public void enqueueTextOrCustomToast(String pkg, IBinder token, + ITransientNotification callback, int duration, int displayId, boolean isCustom) { + enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom); } private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId, - @Nullable ITransientNotificationCallback textCallback) { + @Nullable ITransientNotificationCallback textCallback, boolean isCustom) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration + " displayId=" + displayId); @@ -2716,7 +2736,7 @@ public class NotificationManagerService extends SystemService { return; } - if (callback != null && !appIsForeground && !isSystemToast) { + if (callback != null && !appIsForeground && !isSystemToast && isCustom) { boolean block; long id = Binder.clearCallingIdentity(); try { @@ -3509,7 +3529,7 @@ public class NotificationManagerService extends SystemService { tmp = new StatusBarNotification[mNotificationList.size()]; final int N = mNotificationList.size(); for (int i=0; i<N; i++) { - tmp[i] = mNotificationList.get(i).sbn; + tmp[i] = mNotificationList.get(i).getSbn(); } } } @@ -3541,13 +3561,13 @@ public class NotificationManagerService extends SystemService { final int N = mNotificationList.size(); for (int i = 0; i < N; i++) { StatusBarNotification sbn = sanitizeSbn(pkg, userId, - mNotificationList.get(i).sbn); + mNotificationList.get(i).getSbn()); if (sbn != null) { map.put(sbn.getKey(), sbn); } } for(NotificationRecord snoozed: mSnoozeHelper.getSnoozed(userId, pkg)) { - StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.sbn); + StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.getSbn()); if (sbn != null) { map.put(sbn.getKey(), sbn); } @@ -3555,7 +3575,7 @@ public class NotificationManagerService extends SystemService { final int M = mEnqueuedNotifications.size(); for (int i = 0; i < M; i++) { StatusBarNotification sbn = sanitizeSbn(pkg, userId, - mEnqueuedNotifications.get(i).sbn); + mEnqueuedNotifications.get(i).getSbn()); if (sbn != null) { map.put(sbn.getKey(), sbn); // pending update overwrites existing post here } @@ -3673,15 +3693,15 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < N; i++) { NotificationRecord r = mNotificationsByKey.get(keys[i]); if (r == null) continue; - final int userId = r.sbn.getUserId(); + final int userId = r.getSbn().getUserId(); if (userId != info.userid && userId != UserHandle.USER_ALL && !mUserProfiles.isCurrentProfile(userId)) { throw new SecurityException("Disallowed call from listener: " + info.service); } cancelNotificationFromListenerLocked(info, callingUid, callingPid, - r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId(), - userId); + r.getSbn().getPackageName(), r.getSbn().getTag(), + r.getSbn().getId(), userId); } } else { cancelAllLocked(callingUid, callingPid, info.userid, @@ -3741,7 +3761,7 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < n; i++) { NotificationRecord r = mNotificationsByKey.get(keys[i]); if (r == null) continue; - final int userId = r.sbn.getUserId(); + final int userId = r.getSbn().getUserId(); if (userId != info.userid && userId != UserHandle.USER_ALL && !mUserProfiles.isCurrentProfile(userId)) { throw new SecurityException("Disallowed call from listener: " @@ -3891,7 +3911,7 @@ public class NotificationManagerService extends SystemService { ? mNotificationsByKey.get(keys[i]) : mNotificationList.get(i); if (r == null) continue; - StatusBarNotification sbn = r.sbn; + StatusBarNotification sbn = r.getSbn(); if (!isVisibleToListener(sbn, info)) continue; StatusBarNotification sbnToSend = (trim == TRIM_FULL) ? sbn : sbn.cloneLight(); @@ -3921,7 +3941,7 @@ public class NotificationManagerService extends SystemService { for (int i=0; i < N; i++) { final NotificationRecord r = snoozedRecords.get(i); if (r == null) continue; - StatusBarNotification sbn = r.sbn; + StatusBarNotification sbn = r.getSbn(); if (!isVisibleToListener(sbn, info)) continue; StatusBarNotification sbnToSend = (trim == TRIM_FULL) ? sbn : sbn.cloneLight(); @@ -4096,7 +4116,8 @@ public class NotificationManagerService extends SystemService { Objects.requireNonNull(packageName, "Package name is null"); enforceSystemOrSystemUI("removeAutomaticZenRules"); - return mZenModeHelper.removeAutomaticZenRules(packageName, "removeAutomaticZenRules"); + return mZenModeHelper.removeAutomaticZenRules(packageName, + packageName + "|removeAutomaticZenRules"); } @Override @@ -4411,11 +4432,20 @@ public class NotificationManagerService extends SystemService { policy.priorityCallSenders, policy.priorityMessageSenders, policy.suppressedVisualEffects); } + if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) { + int priorityCategories = correctCategory(policy.priorityCategories, + Policy.PRIORITY_CATEGORY_CONVERSATIONS, + currPolicy.priorityCategories); + + policy = new Policy(priorityCategories, + policy.priorityCallSenders, policy.priorityMessageSenders, + policy.suppressedVisualEffects, currPolicy.priorityConversationSenders); + } int newVisualEffects = calculateSuppressedVisualEffects( policy, currPolicy, applicationInfo.targetSdkVersion); policy = new Policy(policy.priorityCategories, policy.priorityCallSenders, policy.priorityMessageSenders, - newVisualEffects); + newVisualEffects, policy.priorityConversationSenders); ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); mZenModeHelper.setNotificationPolicy(policy); } catch (RemoteException e) { @@ -4424,6 +4454,8 @@ public class NotificationManagerService extends SystemService { } } + + @Override public List<String> getEnabledNotificationListenerPackages() { checkCallerIsSystem(); @@ -4637,8 +4669,8 @@ public class NotificationManagerService extends SystemService { Objects.requireNonNull(user); verifyPrivilegedListener(token, user, true); - return mPreferencesHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user), - false /* includeDeleted */); + return mPreferencesHelper.getNotificationChannels(pkg, + getUidForPackageAndUser(pkg, user), false /* includeDeleted */); } @Override @@ -4830,7 +4862,7 @@ public class NotificationManagerService extends SystemService { if (r == null) { return; } - if (r.sbn.getOverrideGroupKey() == null) { + if (r.getSbn().getOverrideGroupKey() == null) { addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY); EventLogTags.writeNotificationAutogrouped(key); mRankingHandler.requestSort(); @@ -4843,7 +4875,7 @@ public class NotificationManagerService extends SystemService { if (r == null) { return; } - if (r.sbn.getOverrideGroupKey() != null) { + if (r.getSbn().getOverrideGroupKey() != null) { addAutoGroupAdjustment(r, null); EventLogTags.writeNotificationUnautogrouped(key); mRankingHandler.requestSort(); @@ -4853,8 +4885,8 @@ public class NotificationManagerService extends SystemService { private void addAutoGroupAdjustment(NotificationRecord r, String overrideGroupKey) { Bundle signals = new Bundle(); signals.putString(Adjustment.KEY_GROUP_KEY, overrideGroupKey); - Adjustment adjustment = - new Adjustment(r.sbn.getPackageName(), r.getKey(), signals, "", r.sbn.getUserId()); + Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", + r.getSbn().getUserId()); r.addAdjustment(adjustment); } @@ -4890,7 +4922,7 @@ public class NotificationManagerService extends SystemService { // adjustment will post a summary if needed. return; } - final StatusBarNotification adjustedSbn = notificationRecord.sbn; + final StatusBarNotification adjustedSbn = notificationRecord.getSbn(); userId = adjustedSbn.getUser().getIdentifier(); ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId); if (summaries == null) { @@ -4938,7 +4970,8 @@ public class NotificationManagerService extends SystemService { } } if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID, - summaryRecord.sbn.getId(), summaryRecord.sbn.getTag(), summaryRecord, true)) { + summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, + true)) { mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground)); } } @@ -4999,14 +5032,14 @@ public class NotificationManagerService extends SystemService { int N = mNotificationList.size(); for (int i = 0; i < N; i++) { final NotificationRecord nr = mNotificationList.get(i); - if (filter.filtered && !filter.matches(nr.sbn)) continue; + if (filter.filtered && !filter.matches(nr.getSbn())) continue; nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact, NotificationRecordProto.POSTED); } N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { final NotificationRecord nr = mEnqueuedNotifications.get(i); - if (filter.filtered && !filter.matches(nr.sbn)) continue; + if (filter.filtered && !filter.matches(nr.getSbn())) continue; nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact, NotificationRecordProto.ENQUEUED); } @@ -5014,7 +5047,7 @@ public class NotificationManagerService extends SystemService { N = snoozed.size(); for (int i = 0; i < N; i++) { final NotificationRecord nr = snoozed.get(i); - if (filter.filtered && !filter.matches(nr.sbn)) continue; + if (filter.filtered && !filter.matches(nr.getSbn())) continue; nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact, NotificationRecordProto.SNOOZED); } @@ -5075,7 +5108,7 @@ public class NotificationManagerService extends SystemService { pw.println(" Notification List:"); for (int i = 0; i < N; i++) { final NotificationRecord nr = mNotificationList.get(i); - if (filter.filtered && !filter.matches(nr.sbn)) continue; + if (filter.filtered && !filter.matches(nr.getSbn())) continue; nr.dump(pw, " ", getContext(), filter.redact); } pw.println(" "); @@ -5155,7 +5188,7 @@ public class NotificationManagerService extends SystemService { pw.println(" Enqueued Notification List:"); for (int i = 0; i < N; i++) { final NotificationRecord nr = mEnqueuedNotifications.get(i); - if (filter.filtered && !filter.matches(nr.sbn)) continue; + if (filter.filtered && !filter.matches(nr.getSbn())) continue; nr.dump(pw, " ", getContext(), filter.redact); } pw.println(" "); @@ -5281,7 +5314,7 @@ public class NotificationManagerService extends SystemService { if (r == null) { return; } - StatusBarNotification sbn = r.sbn; + StatusBarNotification sbn = r.getSbn(); // NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received @@ -5312,7 +5345,7 @@ public class NotificationManagerService extends SystemService { // Look for the notification, searching both the posted and enqueued lists. NotificationRecord r = findNotificationLocked(pkg, tag, id, userId); if (r != null) { - if (!Objects.equals(opPkg, r.sbn.getOpPkg())) { + if (!Objects.equals(opPkg, r.getSbn().getOpPkg())) { throw new SecurityException(opPkg + " does not have permission to " + "cancel a notification they did not post " + tag + " " + id); } @@ -5360,8 +5393,8 @@ public class NotificationManagerService extends SystemService { try { fixNotification(notification, pkg, tag, id, userId); - } catch (NameNotFoundException e) { - Slog.e(TAG, "Cannot create a context for sending app", e); + } catch (Exception e) { + Slog.e(TAG, "Cannot fix notification", e); return; } @@ -5442,7 +5475,7 @@ public class NotificationManagerService extends SystemService { } if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, - r.sbn.getOverrideGroupKey() != null)) { + r.getSbn().getOverrideGroupKey() != null)) { return; } @@ -5586,12 +5619,12 @@ public class NotificationManagerService extends SystemService { if (shortcutId != null) { // Must track shortcut based bubbles in case the shortcut is removed HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get( - r.sbn.getPackageName()); + r.getSbn().getPackageName()); if (packageBubbles == null) { packageBubbles = new HashMap<>(); } packageBubbles.put(shortcutId, r.getKey()); - mActiveShortcutBubbles.put(r.sbn.getPackageName(), packageBubbles); + mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles); if (!mLauncherAppsCallbackRegistered) { mLauncherAppsService.registerCallback(mLauncherAppsCallback, mHandler); mLauncherAppsCallbackRegistered = true; @@ -5602,12 +5635,12 @@ public class NotificationManagerService extends SystemService { if (shortcutId != null) { // No longer track shortcut HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get( - r.sbn.getPackageName()); + r.getSbn().getPackageName()); if (packageBubbles != null) { packageBubbles.remove(shortcutId); } if (packageBubbles != null && packageBubbles.isEmpty()) { - mActiveShortcutBubbles.remove(r.sbn.getPackageName()); + mActiveShortcutBubbles.remove(r.getSbn().getPackageName()); } if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) { mLauncherAppsService.unregisterCallback(mLauncherAppsCallback); @@ -5853,7 +5886,7 @@ public class NotificationManagerService extends SystemService { */ private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, NotificationRecord r, boolean isAutogroup) { - final String pkg = r.sbn.getPackageName(); + final String pkg = r.getSbn().getPackageName(); final boolean isSystemNotification = isUidSystemOrPhone(uid) || ("android".equals(pkg)); final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg); @@ -5863,7 +5896,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { final int callingUid = Binder.getCallingUid(); - if (mNotificationsByKey.get(r.sbn.getKey()) == null + if (mNotificationsByKey.get(r.getSbn().getKey()) == null && isCallerInstantApp(callingUid, userId)) { // Ephemeral apps have some special constraints for notifications. // They are not allowed to create new notifications however they are allowed to @@ -5874,7 +5907,7 @@ public class NotificationManagerService extends SystemService { } // rate limit updates that aren't completed progress notifications - if (mNotificationsByKey.get(r.sbn.getKey()) != null + if (mNotificationsByKey.get(r.getSbn().getKey()) != null && !r.getNotification().hasCompletedProgress() && !isAutogroup) { @@ -5884,7 +5917,7 @@ public class NotificationManagerService extends SystemService { final long now = SystemClock.elapsedRealtime(); if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate - + ". Shedding " + r.sbn.getKey() + ". package=" + pkg); + + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg); mLastOverRateLogTime = now; } return false; @@ -5933,10 +5966,10 @@ public class NotificationManagerService extends SystemService { final int N = mNotificationList.size(); for (int i = 0; i < N; i++) { final NotificationRecord existing = mNotificationList.get(i); - if (existing.sbn.getPackageName().equals(pkg) - && existing.sbn.getUserId() == userId) { - if (existing.sbn.getId() == excludedId - && TextUtils.equals(existing.sbn.getTag(), excludedTag)) { + if (existing.getSbn().getPackageName().equals(pkg) + && existing.getSbn().getUserId() == userId) { + if (existing.getSbn().getId() == excludedId + && TextUtils.equals(existing.getSbn().getTag(), excludedTag)) { continue; } count++; @@ -5945,8 +5978,8 @@ public class NotificationManagerService extends SystemService { final int M = mEnqueuedNotifications.size(); for (int i = 0; i < M; i++) { final NotificationRecord existing = mEnqueuedNotifications.get(i); - if (existing.sbn.getPackageName().equals(pkg) - && existing.sbn.getUserId() == userId) { + if (existing.getSbn().getPackageName().equals(pkg) + && existing.getSbn().getUserId() == userId) { count++; } } @@ -5965,8 +5998,8 @@ public class NotificationManagerService extends SystemService { } private boolean isBlocked(NotificationRecord r) { - final String pkg = r.sbn.getPackageName(); - final int callingUid = r.sbn.getUid(); + final String pkg = r.getSbn().getPackageName(); + final int callingUid = r.getSbn().getUid(); return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup()) || mPreferencesHelper.getImportance(pkg, callingUid) == NotificationManager.IMPORTANCE_NONE @@ -5996,10 +6029,11 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") void snoozeLocked(NotificationRecord r) { - if (r.sbn.isGroup()) { + if (r.getSbn().isGroup()) { final List<NotificationRecord> groupNotifications = findCurrentAndSnoozedGroupNotificationsLocked( - r.sbn.getPackageName(), r.sbn.getGroupKey(), r.sbn.getUserId()); + r.getSbn().getPackageName(), + r.getSbn().getGroupKey(), r.getSbn().getUserId()); if (r.getNotification().isGroupSummary()) { // snooze all children for (int i = 0; i < groupNotifications.size(); i++) { @@ -6010,7 +6044,7 @@ public class NotificationManagerService extends SystemService { } else { // if there is a valid summary for this group, and we are snoozing the only // child, also snooze the summary - if (mSummaryByGroupKey.containsKey(r.sbn.getGroupKey())) { + if (mSummaryByGroupKey.containsKey(r.getSbn().getGroupKey())) { if (groupNotifications.size() == 2) { // snooze summary and the one child for (int i = 0; i < groupNotifications.size(); i++) { @@ -6041,7 +6075,7 @@ public class NotificationManagerService extends SystemService { cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null); updateLightsLocked(); if (mSnoozeCriterionId != null) { - mAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId); + mAssistants.notifyAssistantSnoozedLocked(r.getSbn(), mSnoozeCriterionId); mSnoozeHelper.snooze(r, mSnoozeCriterionId); } else { mSnoozeHelper.snooze(r, mDuration); @@ -6172,10 +6206,10 @@ public class NotificationManagerService extends SystemService { final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), - r.sbn.getPackageName(), r.sbn.getKey()); + r.getSbn().getPackageName(), r.getSbn().getKey()); final long currentTime = System.currentTimeMillis(); if (snoozeAt.longValue() > currentTime) { - (new SnoozeNotificationRunnable(r.sbn.getKey(), + (new SnoozeNotificationRunnable(r.getSbn().getKey(), snoozeAt.longValue() - currentTime, null)).snoozeLocked(r); return; } @@ -6183,9 +6217,9 @@ public class NotificationManagerService extends SystemService { final String contextId = mSnoozeHelper.getSnoozeContextForUnpostedNotification( r.getUser().getIdentifier(), - r.sbn.getPackageName(), r.sbn.getKey()); + r.getSbn().getPackageName(), r.getSbn().getKey()); if (contextId != null) { - (new SnoozeNotificationRunnable(r.sbn.getKey(), + (new SnoozeNotificationRunnable(r.getSbn().getKey(), 0, contextId)).snoozeLocked(r); return; } @@ -6193,7 +6227,7 @@ public class NotificationManagerService extends SystemService { mEnqueuedNotifications.add(r); scheduleTimeoutLocked(r); - final StatusBarNotification n = r.sbn; + final StatusBarNotification n = r.getSbn(); if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); NotificationRecord old = mNotificationsByKey.get(n.getKey()); if (old != null) { @@ -6291,20 +6325,20 @@ public class NotificationManagerService extends SystemService { } final boolean isPackageSuspended = - isPackagePausedOrSuspended(r.sbn.getPackageName(), r.getUid()); + isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid()); r.setHidden(isPackageSuspended); if (isPackageSuspended) { mUsageStats.registerSuspendedByAdmin(r); } NotificationRecord old = mNotificationsByKey.get(key); - final StatusBarNotification n = r.sbn; + final StatusBarNotification n = r.getSbn(); final Notification notification = n.getNotification(); // Make sure the SBN has an instance ID for statsd logging. - if (old == null || old.sbn.getInstanceId() == null) { + if (old == null || old.getSbn().getInstanceId() == null) { n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); } else { - n.setInstanceId(old.sbn.getInstanceId()); + n.setInstanceId(old.getSbn().getInstanceId()); } int index = indexOfNotificationLocked(n.getKey()); @@ -6344,7 +6378,7 @@ public class NotificationManagerService extends SystemService { } if (notification.getSmallIcon() != null) { - StatusBarNotification oldSbn = (old != null) ? old.sbn : null; + StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; mListeners.notifyPostedLocked(r, old); if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) && !isCritical(r)) { @@ -6358,7 +6392,7 @@ public class NotificationManagerService extends SystemService { } else if (oldSbn != null) { final NotificationRecord finalRecord = r; mHandler.post(() -> mGroupHelper.onNotificationUpdated( - finalRecord.sbn, hasAutoGroupSummaryLocked(n))); + finalRecord.getSbn(), hasAutoGroupSummaryLocked(n))); } } else { Slog.e(TAG, "Not posting notification without small icon: " + notification); @@ -6405,7 +6439,7 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) { // Ignore summary updates because we don't display most of the information. - if (r.sbn.isGroup() && r.sbn.getNotification().isGroupSummary()) { + if (r.getSbn().isGroup() && r.getSbn().getNotification().isGroupSummary()) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " + r.getKey() + " is not interruptive: summary"); @@ -6429,8 +6463,8 @@ public class NotificationManagerService extends SystemService { return false; } - Notification oldN = old.sbn.getNotification(); - Notification newN = r.sbn.getNotification(); + Notification oldN = old.getSbn().getNotification(); + Notification newN = r.getSbn().getNotification(); if (oldN.extras == null || newN.extras == null) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " @@ -6441,7 +6475,7 @@ public class NotificationManagerService extends SystemService { // Ignore visual interruptions from foreground services because users // consider them one 'session'. Count them for everything else. - if ((r.sbn.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0) { + if ((r.getSbn().getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " + r.getKey() + " is not interruptive: foreground service"); @@ -6555,7 +6589,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old, int callingUid, int callingPid) { - StatusBarNotification sbn = r.sbn; + StatusBarNotification sbn = r.getSbn(); Notification n = sbn.getNotification(); if (n.isGroupSummary() && !sbn.isAppGroup()) { // notifications without a group shouldn't be a summary, otherwise autobundling can @@ -6566,8 +6600,8 @@ public class NotificationManagerService extends SystemService { String group = sbn.getGroupKey(); boolean isSummary = n.isGroupSummary(); - Notification oldN = old != null ? old.sbn.getNotification() : null; - String oldGroup = old != null ? old.sbn.getGroupKey() : null; + Notification oldN = old != null ? old.getSbn().getNotification() : null; + String oldGroup = old != null ? old.getSbn().getGroupKey() : null; boolean oldIsSummary = old != null && oldN.isGroupSummary(); if (oldIsSummary) { @@ -6624,7 +6658,7 @@ public class NotificationManagerService extends SystemService { boolean beep = false; boolean blink = false; - final Notification notification = record.sbn.getNotification(); + final Notification notification = record.getSbn().getNotification(); final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED? @@ -6642,7 +6676,7 @@ public class NotificationManagerService extends SystemService { // If the notification will appear in the status bar, it should send an accessibility // event if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) { - sendAccessibilityEvent(notification, record.sbn.getPackageName()); + sendAccessibilityEvent(notification, record.getSbn().getPackageName()); sentAccessibilityEvent = true; } @@ -6664,7 +6698,7 @@ public class NotificationManagerService extends SystemService { boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) { if (!sentAccessibilityEvent) { - sendAccessibilityEvent(notification, record.sbn.getPackageName()); + sendAccessibilityEvent(notification, record.getSbn().getPackageName()); sentAccessibilityEvent = true; } if (DBG) Slog.v(TAG, "Interrupting!"); @@ -6719,7 +6753,7 @@ public class NotificationManagerService extends SystemService { final int buzzBeepBlink = (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0); if (buzzBeepBlink > 0) { // Ignore summary updates because we don't display most of the information. - if (record.sbn.isGroup() && record.sbn.getNotification().isGroupSummary()) { + if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) { if (DEBUG_INTERRUPTIVENESS) { Slog.v(TAG, "INTERRUPTIVENESS: " + record.getKey() + " is not interruptive: summary"); @@ -6774,7 +6808,7 @@ public class NotificationManagerService extends SystemService { return false; } // Suppressed because another notification in its group handles alerting - if (record.sbn.isGroup() && record.getNotification().suppressAlertingDueToGrouping()) { + if (record.getSbn().isGroup() && record.getNotification().suppressAlertingDueToGrouping()) { return false; } // not if in call or the screen's on @@ -6806,14 +6840,14 @@ public class NotificationManagerService extends SystemService { } // Suppressed because another notification in its group handles alerting - if (record.sbn.isGroup()) { + if (record.getSbn().isGroup()) { if (notification.suppressAlertingDueToGrouping()) { return true; } } // Suppressed for being too recently noisy - final String pkg = record.sbn.getPackageName(); + final String pkg = record.getSbn().getPackageName(); if (mUsageStats.isAlertRateLimited(pkg)) { Slog.e(TAG, "Muting recently noisy " + record.getKey()); return true; @@ -6854,7 +6888,7 @@ public class NotificationManagerService extends SystemService { if (player != null) { if (DBG) Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + record.getAudioAttributes()); - player.playAsync(soundUri, record.sbn.getUser(), looping, + player.playAsync(soundUri, record.getSbn().getUser(), looping, record.getAudioAttributes()); return true; } @@ -6897,15 +6931,16 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), + mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(), effect, "Notification (delayed)", record.getAudioAttributes()); } else { - Slog.e(TAG, "No vibration for canceled notification : " + record.getKey()); + Slog.e(TAG, "No vibration for canceled notification : " + + record.getKey()); } } }).start(); } else { - mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(), + mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(), effect, "Notification", record.getAudioAttributes()); } return true; @@ -7388,7 +7423,7 @@ public class NotificationManagerService extends SystemService { if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) != null) { mNotificationList.remove(recordInList); - mNotificationsByKey.remove(recordInList.sbn.getKey()); + mNotificationsByKey.remove(recordInList.getSbn().getKey()); wasPosted = true; } while ((recordInList = findNotificationByListLocked(mEnqueuedNotifications, r.getKey())) @@ -7429,7 +7464,7 @@ public class NotificationManagerService extends SystemService { } catch (PendingIntent.CanceledException ex) { // do nothing - there's no relevant way to recover, and // no reason to let this propagate - Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex); + Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex); } } } @@ -7445,7 +7480,7 @@ public class NotificationManagerService extends SystemService { mHandler.post(new Runnable() { @Override public void run() { - mGroupHelper.onNotificationRemoved(r.sbn); + mGroupHelper.onNotificationRemoved(r.getSbn()); } }); } @@ -7501,13 +7536,15 @@ public class NotificationManagerService extends SystemService { if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) { mSummaryByGroupKey.remove(groupKey); } - final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId()); - if (summaries != null && r.sbn.getKey().equals(summaries.get(r.sbn.getPackageName()))) { - summaries.remove(r.sbn.getPackageName()); + final ArrayMap<String, String> summaries = + mAutobundledSummaries.get(r.getSbn().getUserId()); + if (summaries != null && r.getSbn().getKey().equals( + summaries.get(r.getSbn().getPackageName()))) { + summaries.remove(r.getSbn().getPackageName()); } // Save it for users of getHistoricalNotifications() - mArchive.record(r.sbn); + mArchive.record(r.getSbn()); final long now = System.currentTimeMillis(); final LogMaker logMaker = r.getItemLogMaker() @@ -7563,7 +7600,7 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < newUris.size(); i++) { final Uri uri = newUris.valueAt(i); if (oldUris == null || !oldUris.contains(uri)) { - if (DBG) Slog.d(TAG, key + ": granting " + uri); + Slog.d(TAG, key + ": granting " + uri); grantUriPermission(permissionOwner, uri, newRecord.getUid(), targetPkg, targetUserId); } @@ -7600,6 +7637,8 @@ public class NotificationManagerService extends SystemService { targetUserId); } catch (RemoteException ignored) { // Ignored because we're in same process + } catch (SecurityException e) { + Slog.e(TAG, "Cannot grant uri access; " + sourceUid + " does not own " + uri); } finally { Binder.restoreCallingIdentity(ident); } @@ -7756,7 +7795,7 @@ public class NotificationManagerService extends SystemService { if (!flagChecker.apply(r.getFlags())) { continue; } - if (pkg != null && !r.sbn.getPackageName().equals(pkg)) { + if (pkg != null && !r.getSbn().getPackageName().equals(pkg)) { continue; } if (channelId != null && !channelId.equals(r.getChannel().getId())) { @@ -7852,7 +7891,7 @@ public class NotificationManagerService extends SystemService { return; } - String pkg = r.sbn.getPackageName(); + String pkg = r.getSbn().getPackageName(); if (pkg == null) { if (DBG) Slog.e(TAG, "No package for group summary: " + r.getKey()); @@ -7869,12 +7908,12 @@ public class NotificationManagerService extends SystemService { private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList, NotificationRecord parentNotification, int callingUid, int callingPid, String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) { - final String pkg = parentNotification.sbn.getPackageName(); + final String pkg = parentNotification.getSbn().getPackageName(); final int userId = parentNotification.getUserId(); final int reason = REASON_GROUP_SUMMARY_CANCELED; for (int i = notificationList.size() - 1; i >= 0; i--) { final NotificationRecord childR = notificationList.get(i); - final StatusBarNotification childSbn = childR.sbn; + final StatusBarNotification childSbn = childR.getSbn(); if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) && childR.getGroupKey().equals(parentNotification.getGroupKey()) && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0 @@ -7952,7 +7991,7 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey) - && r.sbn.getPackageName().equals(pkg)) { + && r.getSbn().getPackageName().equals(pkg)) { records.add(r); } } @@ -7993,8 +8032,9 @@ public class NotificationManagerService extends SystemService { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); - if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id && - TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) { + if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id && + TextUtils.equals(r.getSbn().getTag(), tag) + && r.getSbn().getPackageName().equals(pkg)) { return r; } } @@ -8008,8 +8048,9 @@ public class NotificationManagerService extends SystemService { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); - if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id && - TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) { + if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id && + TextUtils.equals(r.getSbn().getTag(), tag) + && r.getSbn().getPackageName().equals(pkg)) { matching.add(r); } } @@ -8047,7 +8088,7 @@ public class NotificationManagerService extends SystemService { int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - if (pkgList.contains(rec.sbn.getPackageName())) { + if (pkgList.contains(rec.getSbn().getPackageName())) { rec.setHidden(true); changedNotifications.add(rec); } @@ -8065,7 +8106,7 @@ public class NotificationManagerService extends SystemService { int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - if (pkgList.contains(rec.sbn.getPackageName())) { + if (pkgList.contains(rec.getSbn().getPackageName())) { rec.setHidden(false); changedNotifications.add(rec); } @@ -8264,10 +8305,10 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); - if (!isVisibleToListener(record.sbn, info)) { + if (!isVisibleToListener(record.getSbn(), info)) { continue; } - final String key = record.sbn.getKey(); + final String key = record.getSbn().getKey(); final NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); ranking.populate( @@ -8278,7 +8319,7 @@ public class NotificationManagerService extends SystemService { record.getSuppressedVisualEffects(), record.getImportance(), record.getImportanceExplanation(), - record.sbn.getOverrideGroupKey(), + record.getSbn().getOverrideGroupKey(), record.getChannel(), record.getPeopleOverride(), record.getSnoozeCriteria(), @@ -8550,7 +8591,7 @@ public class NotificationManagerService extends SystemService { for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { ArrayList<String> keys = new ArrayList<>(records.size()); for (NotificationRecord r : records) { - boolean sbnVisible = isVisibleToListener(r.sbn, info) + boolean sbnVisible = isVisibleToListener(r.getSbn(), info) && info.isSameUser(r.getUserId()); if (sbnVisible) { keys.add(r.getKey()); @@ -8638,7 +8679,7 @@ public class NotificationManagerService extends SystemService { if (debug) { Slog.v(TAG, "onNotificationEnqueuedLocked() called with: r = [" + r + "]"); } - final StatusBarNotification sbn = r.sbn; + final StatusBarNotification sbn = r.getSbn(); notifyAssistantLocked( sbn, true /* sameUserOnly */, @@ -8977,59 +9018,64 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { - // Lazily initialized snapshots of the notification. - StatusBarNotification sbn = r.sbn; - StatusBarNotification oldSbn = (old != null) ? old.sbn : null; - TrimCache trimCache = new TrimCache(sbn); + try { + // Lazily initialized snapshots of the notification. + StatusBarNotification sbn = r.getSbn(); + StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; + TrimCache trimCache = new TrimCache(sbn); + + for (final ManagedServiceInfo info : getServices()) { + boolean sbnVisible = isVisibleToListener(sbn, info); + boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) + : false; + // This notification hasn't been and still isn't visible -> ignore. + if (!oldSbnVisible && !sbnVisible) { + continue; + } + // If the notification is hidden, don't notifyPosted listeners targeting < P. + // Instead, those listeners will receive notifyPosted when the notification is + // unhidden. + if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) { + continue; + } - for (final ManagedServiceInfo info : getServices()) { - boolean sbnVisible = isVisibleToListener(sbn, info); - boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; - // This notification hasn't been and still isn't visible -> ignore. - if (!oldSbnVisible && !sbnVisible) { - continue; - } - // If the notification is hidden, don't notifyPosted listeners targeting < P. - // Instead, those listeners will receive notifyPosted when the notification is - // unhidden. - if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) { - continue; - } + // If we shouldn't notify all listeners, this means the hidden state of + // a notification was changed. Don't notifyPosted listeners targeting >= P. + // Instead, those listeners will receive notifyRankingUpdate. + if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) { + continue; + } - // If we shouldn't notify all listeners, this means the hidden state of - // a notification was changed. Don't notifyPosted listeners targeting >= P. - // Instead, those listeners will receive notifyRankingUpdate. - if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) { - continue; - } + final NotificationRankingUpdate update = makeRankingUpdateLocked(info); - final NotificationRankingUpdate update = makeRankingUpdateLocked(info); + // This notification became invisible -> remove the old one. + if (oldSbnVisible && !sbnVisible) { + final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight(); + mHandler.post(new Runnable() { + @Override + public void run() { + notifyRemoved( + info, oldSbnLightClone, update, null, REASON_USER_STOPPED); + } + }); + continue; + } - // This notification became invisible -> remove the old one. - if (oldSbnVisible && !sbnVisible) { - final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight(); + // Grant access before listener is notified + final int targetUserId = (info.userid == UserHandle.USER_ALL) + ? UserHandle.USER_SYSTEM : info.userid; + updateUriPermissions(r, old, info.component.getPackageName(), targetUserId); + + final StatusBarNotification sbnToPost = trimCache.ForListener(info); mHandler.post(new Runnable() { @Override public void run() { - notifyRemoved( - info, oldSbnLightClone, update, null, REASON_USER_STOPPED); + notifyPosted(info, sbnToPost, update); } }); - continue; } - - // Grant access before listener is notified - final int targetUserId = (info.userid == UserHandle.USER_ALL) - ? UserHandle.USER_SYSTEM : info.userid; - updateUriPermissions(r, old, info.component.getPackageName(), targetUserId); - - final StatusBarNotification sbnToPost = trimCache.ForListener(info); - mHandler.post(new Runnable() { - @Override - public void run() { - notifyPosted(info, sbnToPost, update); - } - }); + } catch (Exception e) { + Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e); } } @@ -9039,7 +9085,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { - final StatusBarNotification sbn = r.sbn; + final StatusBarNotification sbn = r.getSbn(); // make a copy in case changes are made to the underlying Notification object // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the @@ -9103,7 +9149,7 @@ public class NotificationManagerService extends SystemService { if (isHiddenRankingUpdate && serviceInfo.targetSdkVersion >= Build.VERSION_CODES.P) { for (NotificationRecord rec : changedHiddenNotifications) { - if (isVisibleToListener(rec.sbn, serviceInfo)) { + if (isVisibleToListener(rec.getSbn(), serviceInfo)) { notifyThisListener = true; break; } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 660d574fe64e..4785da9a5922 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -91,7 +91,7 @@ public final class NotificationRecord { static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); // the period after which a notification is updated where it can make sound private static final int MAX_SOUND_DELAY_MS = 2000; - final StatusBarNotification sbn; + private final StatusBarNotification sbn; IActivityManager mAm; UriGrantsManagerInternal mUgmInternal; final int mTargetSdkVersion; @@ -229,7 +229,7 @@ public final class NotificationRecord { } private Uri calculateSound() { - final Notification n = sbn.getNotification(); + final Notification n = getSbn().getNotification(); // No notification sounds on tv if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { @@ -265,7 +265,7 @@ public final class NotificationRecord { if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { - final Notification notification = sbn.getNotification(); + final Notification notification = getSbn().getNotification(); if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { light = new Light(notification.ledARGB, notification.ledOnMS, notification.ledOffMS); @@ -296,7 +296,7 @@ public final class NotificationRecord { if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { - final Notification notification = sbn.getNotification(); + final Notification notification = getSbn().getNotification(); final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if (useDefaultVibrate) { @@ -309,7 +309,7 @@ public final class NotificationRecord { } private AudioAttributes calculateAttributes() { - final Notification n = sbn.getNotification(); + final Notification n = getSbn().getNotification(); AudioAttributes attributes = getChannel().getAudioAttributes(); if (attributes == null) { attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; @@ -335,7 +335,7 @@ public final class NotificationRecord { } private int calculateInitialImportance() { - final Notification n = sbn.getNotification(); + final Notification n = getSbn().getNotification(); int importance = getChannel().getImportance(); // Post-channels notifications use this mInitialImportanceExplanationCode = getChannel().hasUserSetImportance() ? MetricsEvent.IMPORTANCE_EXPLANATION_USER @@ -406,31 +406,31 @@ public final class NotificationRecord { mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); mCreationTimeMs = previous.mCreationTimeMs; mVisibleSinceMs = previous.mVisibleSinceMs; - if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) { - sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey()); + if (previous.getSbn().getOverrideGroupKey() != null && !getSbn().isAppGroup()) { + getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey()); } // Don't copy importance information or mGlobalSortKey, recompute them. } - public Notification getNotification() { return sbn.getNotification(); } - public int getFlags() { return sbn.getNotification().flags; } - public UserHandle getUser() { return sbn.getUser(); } - public String getKey() { return sbn.getKey(); } + public Notification getNotification() { return getSbn().getNotification(); } + public int getFlags() { return getSbn().getNotification().flags; } + public UserHandle getUser() { return getSbn().getUser(); } + public String getKey() { return getSbn().getKey(); } /** @deprecated Use {@link #getUser()} instead. */ - public int getUserId() { return sbn.getUserId(); } - public int getUid() { return sbn.getUid(); } + public int getUserId() { return getSbn().getUserId(); } + public int getUid() { return getSbn().getUid(); } void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) { final long token = proto.start(fieldId); - proto.write(NotificationRecordProto.KEY, sbn.getKey()); + proto.write(NotificationRecordProto.KEY, getSbn().getKey()); proto.write(NotificationRecordProto.STATE, state); if (getChannel() != null) { proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId()); } proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null); proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null); - proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags); + proto.write(NotificationRecordProto.FLAGS, getSbn().getNotification().flags); proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey()); proto.write(NotificationRecordProto.IMPORTANCE, getImportance()); if (getSound() != null) { @@ -439,8 +439,8 @@ public final class NotificationRecord { if (getAudioAttributes() != null) { getAudioAttributes().dumpDebug(proto, NotificationRecordProto.AUDIO_ATTRIBUTES); } - proto.write(NotificationRecordProto.PACKAGE, sbn.getPackageName()); - proto.write(NotificationRecordProto.DELEGATE_PACKAGE, sbn.getOpPkg()); + proto.write(NotificationRecordProto.PACKAGE, getSbn().getPackageName()); + proto.write(NotificationRecordProto.DELEGATE_PACKAGE, getSbn().getOpPkg()); proto.end(token); } @@ -452,15 +452,15 @@ public final class NotificationRecord { } void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) { - final Notification notification = sbn.getNotification(); + final Notification notification = getSbn().getNotification(); pw.println(prefix + this); prefix = prefix + " "; - pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); - pw.println(prefix + "opPkg=" + sbn.getOpPkg()); + pw.println(prefix + "uid=" + getSbn().getUid() + " userId=" + getSbn().getUserId()); + pw.println(prefix + "opPkg=" + getSbn().getOpPkg()); pw.println(prefix + "icon=" + notification.getSmallIcon()); pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags)); pw.println(prefix + "pri=" + notification.priority); - pw.println(prefix + "key=" + sbn.getKey()); + pw.println(prefix + "key=" + getSbn().getKey()); pw.println(prefix + "seen=" + mStats.hasSeen()); pw.println(prefix + "groupKey=" + getGroupKey()); pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent); @@ -594,9 +594,9 @@ public final class NotificationRecord { "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" + ": %s)", System.identityHashCode(this), - this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), - this.sbn.getTag(), this.mImportance, this.sbn.getKey(), - this.sbn.getNotification()); + this.getSbn().getPackageName(), this.getSbn().getUser(), this.getSbn().getId(), + this.getSbn().getTag(), this.mImportance, this.getSbn().getKey(), + this.getSbn().getNotification()); } public boolean hasAdjustment(String key) { @@ -936,7 +936,7 @@ public final class NotificationRecord { private long calculateRankingTimeMs(long previousRankingTimeMs) { Notification n = getNotification(); // Take developer provided 'when', unless it's in the future. - if (n.when != 0 && n.when <= sbn.getPostTime()) { + if (n.when != 0 && n.when <= getSbn().getPostTime()) { return n.when; } // If we've ranked a previous instance with a timestamp, inherit it. This case is @@ -944,7 +944,7 @@ public final class NotificationRecord { if (previousRankingTimeMs > 0) { return previousRankingTimeMs; } - return sbn.getPostTime(); + return getSbn().getPostTime(); } public void setGlobalSortKey(String globalSortKey) { @@ -977,11 +977,11 @@ public final class NotificationRecord { } public String getGroupKey() { - return sbn.getGroupKey(); + return getSbn().getGroupKey(); } public void setOverrideGroupKey(String overrideGroupKey) { - sbn.setOverrideGroupKey(overrideGroupKey); + getSbn().setOverrideGroupKey(overrideGroupKey); } public NotificationChannel getChannel() { @@ -1202,7 +1202,7 @@ public final class NotificationRecord { * Returns whether this notification was posted by a secondary app */ public boolean isProxied() { - return !Objects.equals(sbn.getPackageName(), sbn.getOpPkg()); + return !Objects.equals(getSbn().getPackageName(), getSbn().getOpPkg()); } /** @@ -1245,7 +1245,7 @@ public final class NotificationRecord { if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return; // We can't grant Uri permissions from system - final int sourceUid = sbn.getUid(); + final int sourceUid = getSbn().getUid(); if (sourceUid == android.os.Process.SYSTEM_UID) return; final long ident = Binder.clearCallingIdentity(); @@ -1274,7 +1274,7 @@ public final class NotificationRecord { } public LogMaker getLogMaker(long now) { - LogMaker lm = sbn.getLogMaker() + LogMaker lm = getSbn().getLogMaker() .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance) .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now)) .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now)) @@ -1351,6 +1351,10 @@ public final class NotificationRecord { return true; } + StatusBarNotification getSbn() { + return sbn; + } + @VisibleForTesting static final class Light { public final int color; diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 9bbc39249e2e..8d8511f030f0 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -88,12 +88,12 @@ public interface NotificationRecordLogger { return true; } - return !(Objects.equals(r.sbn.getChannelIdLogTag(), old.sbn.getChannelIdLogTag()) - && Objects.equals(r.sbn.getGroupLogTag(), old.sbn.getGroupLogTag()) - && (r.sbn.getNotification().isGroupSummary() - == old.sbn.getNotification().isGroupSummary()) - && Objects.equals(r.sbn.getNotification().category, - old.sbn.getNotification().category) + return !(Objects.equals(r.getSbn().getChannelIdLogTag(), old.getSbn().getChannelIdLogTag()) + && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag()) + && (r.getSbn().getNotification().isGroupSummary() + == old.getSbn().getNotification().isGroupSummary()) + && Objects.equals(r.getSbn().getNotification().category, + old.getSbn().getNotification().category) && (r.getImportance() == old.getImportance())); } @@ -106,7 +106,7 @@ public interface NotificationRecordLogger { * @return hash code for the notification style class, or 0 if none exists. */ public int getStyle() { - return getStyle(r.sbn.getNotification().extras); + return getStyle(r.getSbn().getNotification().extras); } private int getStyle(@Nullable Bundle extras) { @@ -120,7 +120,7 @@ public interface NotificationRecordLogger { } int getNumPeople() { - return getNumPeople(r.sbn.getNotification().extras); + return getNumPeople(r.getSbn().getNotification().extras); } private int getNumPeople(@Nullable Bundle extras) { @@ -140,7 +140,7 @@ public interface NotificationRecordLogger { } int getInstanceId() { - return (r.sbn.getInstanceId() == null ? 0 : r.sbn.getInstanceId().getId()); + return (r.getSbn().getInstanceId() == null ? 0 : r.getSbn().getInstanceId().getId()); } } } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index d61379921f2e..4974c3001b9b 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -34,15 +34,15 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED, /* int32 event_id = 1 */ p.getUiEvent().getId(), /* int32 uid = 2 */ r.getUid(), - /* string package_name = 3 */ r.sbn.getPackageName(), + /* string package_name = 3 */ r.getSbn().getPackageName(), /* int32 instance_id = 4 */ p.getInstanceId(), - /* int32 notification_id = 5 */ r.sbn.getId(), - /* string notification_tag = 6 */ r.sbn.getTag(), - /* string channel_id = 7 */ r.sbn.getChannelIdLogTag(), - /* string group_id = 8 */ r.sbn.getGroupLogTag(), + /* int32 notification_id = 5 */ r.getSbn().getId(), + /* string notification_tag = 6 */ r.getSbn().getTag(), + /* string channel_id = 7 */ r.getSbn().getChannelIdLogTag(), + /* string group_id = 8 */ r.getSbn().getGroupLogTag(), /* int32 group_instance_id = 9 */ 0, // TODO generate and fill instance ids - /* bool is_group_summary = 10 */ r.sbn.getNotification().isGroupSummary(), - /* string category = 11 */ r.sbn.getNotification().category, + /* bool is_group_summary = 10 */ r.getSbn().getNotification().isGroupSummary(), + /* string category = 11 */ r.getSbn().getNotification().category, /* int32 style = 12 */ p.getStyle(), /* int32 num_people = 13 */ p.getNumPeople(), /* int32 position = 14 */ position, diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index d1fe0d9351cb..b42fe929549a 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -276,7 +276,7 @@ public class NotificationUsageStats { // Locked by this. private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { - return getAggregatedStatsLocked(record.sbn.getPackageName()); + return getAggregatedStatsLocked(record.getSbn().getPackageName()); } // Locked by this. @@ -1142,7 +1142,7 @@ public class NotificationUsageStats { long nowMs = System.currentTimeMillis(); switch (msg.what) { case MSG_POST: - writeEvent(r.sbn.getPostTime(), EVENT_TYPE_POST, r); + writeEvent(r.getSbn().getPostTime(), EVENT_TYPE_POST, r); break; case MSG_CLICK: writeEvent(nowMs, EVENT_TYPE_CLICK, r); @@ -1287,7 +1287,7 @@ public class NotificationUsageStats { private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) { ContentValues cv = new ContentValues(); - cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier()); + cv.put(COL_EVENT_USER_ID, r.getSbn().getUser().getIdentifier()); cv.put(COL_EVENT_TIME, eventTimeMs); cv.put(COL_EVENT_TYPE, eventType); putNotificationIdentifiers(r, cv); @@ -1324,16 +1324,16 @@ public class NotificationUsageStats { } private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) { - outCv.put(COL_KEY, r.sbn.getKey()); - outCv.put(COL_PKG, r.sbn.getPackageName()); + outCv.put(COL_KEY, r.getSbn().getKey()); + outCv.put(COL_PKG, r.getSbn().getPackageName()); } private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) { - outCv.put(COL_NOTIFICATION_ID, r.sbn.getId()); - if (r.sbn.getTag() != null) { - outCv.put(COL_TAG, r.sbn.getTag()); + outCv.put(COL_NOTIFICATION_ID, r.getSbn().getId()); + if (r.getSbn().getTag() != null) { + outCv.put(COL_TAG, r.getSbn().getTag()); } - outCv.put(COL_WHEN_MS, r.sbn.getPostTime()); + outCv.put(COL_WHEN_MS, r.getSbn().getPostTime()); outCv.put(COL_FLAGS, r.getNotification().flags); final int before = r.stats.requestedImportance; final int after = r.getImportance(); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index b0c1863ea31d..fe39322edb42 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1380,7 +1380,8 @@ public class PreferencesHelper implements RankingConfig { policy.priorityCategories, policy.priorityCallSenders, policy.priorityMessageSenders, policy.suppressedVisualEffects, (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND - : 0))); + : 0), + policy.priorityConversationSenders)); } public boolean areChannelsBypassingDnd() { diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 9e32d0e81a47..661297a7346e 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -166,7 +166,7 @@ public class SnoozeHelper { ArrayMap<String, NotificationRecord> packages = mSnoozedNotifications.get(userId).get(pkg); for (int i = 0; i < packages.size(); i++) { - String currentGroupKey = packages.valueAt(i).sbn.getGroup(); + String currentGroupKey = packages.valueAt(i).getSbn().getGroup(); if (currentGroupKey.equals(groupKey)) { records.add(packages.valueAt(i)); } @@ -223,7 +223,7 @@ public class SnoozeHelper { * Snoozes a notification and schedules an alarm to repost at that time. */ protected void snooze(NotificationRecord record, long duration) { - String pkg = record.sbn.getPackageName(); + String pkg = record.getSbn().getPackageName(); String key = record.getKey(); int userId = record.getUser().getIdentifier(); @@ -242,7 +242,7 @@ public class SnoozeHelper { int userId = record.getUser().getIdentifier(); if (contextId != null) { synchronized (mPersistedSnoozedNotificationsWithContext) { - storeRecord(record.sbn.getPackageName(), record.getKey(), + storeRecord(record.getSbn().getPackageName(), record.getKey(), userId, mPersistedSnoozedNotificationsWithContext, contextId); } } @@ -254,9 +254,9 @@ public class SnoozeHelper { if (DEBUG) { Slog.d(TAG, "Snoozing " + record.getKey()); } - storeRecord(record.sbn.getPackageName(), record.getKey(), + storeRecord(record.getSbn().getPackageName(), record.getKey(), userId, mSnoozedNotifications, record); - mPackages.put(record.getKey(), record.sbn.getPackageName()); + mPackages.put(record.getKey(), record.getSbn().getPackageName()); mUsers.put(record.getKey(), userId); } @@ -308,7 +308,7 @@ public class SnoozeHelper { if (recordsForPkg != null) { final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); for (Map.Entry<String, NotificationRecord> record : records) { - final StatusBarNotification sbn = record.getValue().sbn; + final StatusBarNotification sbn = record.getValue().getSbn(); if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { record.getValue().isCanceled = true; return true; @@ -369,7 +369,7 @@ public class SnoozeHelper { if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName()); + ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName()); if (pkgRecords == null) { return; } @@ -420,7 +420,7 @@ public class SnoozeHelper { int N = recordsByKey.size(); for (int i = 0; i < N; i++) { final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); - if (potentialGroupSummary.sbn.isGroup() + if (potentialGroupSummary.getSbn().isGroup() && potentialGroupSummary.getNotification().isGroupSummary() && groupKey.equals(potentialGroupSummary.getGroupKey())) { groupSummaryKey = potentialGroupSummary.getKey(); diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 639cc70fa275..90fc59a893a6 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -38,6 +38,8 @@ import android.util.Log; import android.util.LruCache; import android.util.Slog; +import libcore.util.EmptyArray; + import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; @@ -301,7 +303,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { for (String person: second) { people.add(person); } - return (String[]) people.toArray(); + return people.toArray(EmptyArray.STRING); } @Nullable diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index 6045f6c73b93..4d1985590d7d 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -17,6 +17,7 @@ package com.android.server.notification; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE; import android.app.Notification; import android.app.NotificationManager; @@ -106,8 +107,8 @@ public class ZenModeFiltering { } private static Bundle extras(NotificationRecord record) { - return record != null && record.sbn != null && record.sbn.getNotification() != null - ? record.sbn.getNotification().extras : null; + return record != null && record.getSbn() != null && record.getSbn().getNotification() != null + ? record.getSbn().getNotification().extras : null; } protected void recordCall(NotificationRecord record) { @@ -125,8 +126,8 @@ public class ZenModeFiltering { } // Make an exception to policy for the notification saying that policy has changed if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects) - && "android".equals(record.sbn.getPackageName()) - && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.sbn.getId()) { + && "android".equals(record.getSbn().getPackageName()) + && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) { ZenLog.traceNotIntercepted(record, "systemDndChangedNotification"); return false; } @@ -156,25 +157,6 @@ public class ZenModeFiltering { } return false; } - if (isCall(record)) { - if (policy.allowRepeatCallers() - && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { - ZenLog.traceNotIntercepted(record, "repeatCaller"); - return false; - } - if (!policy.allowCalls()) { - ZenLog.traceIntercepted(record, "!allowCalls"); - return true; - } - return shouldInterceptAudience(policy.allowCallsFrom(), record); - } - if (isMessage(record)) { - if (!policy.allowMessages()) { - ZenLog.traceIntercepted(record, "!allowMessages"); - return true; - } - return shouldInterceptAudience(policy.allowMessagesFrom(), record); - } if (isEvent(record)) { if (!policy.allowEvents()) { ZenLog.traceIntercepted(record, "!allowEvents"); @@ -203,6 +185,41 @@ public class ZenModeFiltering { } return false; } + if (isConversation(record)) { + if (policy.allowConversations()) { + if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) { + ZenLog.traceNotIntercepted(record, "conversationAnyone"); + return false; + } else if (policy.priorityConversationSenders + == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT + && record.getChannel().isImportantConversation()) { + ZenLog.traceNotIntercepted(record, "conversationMatches"); + return false; + } + } + // if conversations aren't allowed record might still be allowed thanks + // to call or message metadata, so don't return yet + } + if (isCall(record)) { + if (policy.allowRepeatCallers() + && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { + ZenLog.traceNotIntercepted(record, "repeatCaller"); + return false; + } + if (!policy.allowCalls()) { + ZenLog.traceIntercepted(record, "!allowCalls"); + return true; + } + return shouldInterceptAudience(policy.allowCallsFrom(), record); + } + if (isMessage(record)) { + if (!policy.allowMessages()) { + ZenLog.traceIntercepted(record, "!allowMessages"); + return true; + } + return shouldInterceptAudience(policy.allowMessagesFrom(), record); + } + ZenLog.traceIntercepted(record, "!priority"); return true; default: @@ -245,7 +262,7 @@ public class ZenModeFiltering { } public boolean isCall(NotificationRecord record) { - return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) + return record != null && (isDefaultPhoneApp(record.getSbn().getPackageName()) || record.isCategory(Notification.CATEGORY_CALL)); } @@ -273,7 +290,11 @@ public class ZenModeFiltering { } protected boolean isMessage(NotificationRecord record) { - return mMessagingUtil.isMessaging(record.sbn); + return mMessagingUtil.isMessaging(record.getSbn()); + } + + protected boolean isConversation(NotificationRecord record) { + return record.isConversation(); } private static boolean audienceMatches(int source, float contactAffinity) { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 696d2ea02600..3b564c3a2459 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -641,9 +641,11 @@ public class ZenModeHelper { } public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mZenMode="); + pw.print(prefix); + pw.print("mZenMode="); pw.println(Global.zenModeToString(mZenMode)); - pw.print("mConsolidatedPolicy=" + mConsolidatedPolicy.toString()); + pw.print(prefix); + pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString()); final int N = mConfigs.size(); for (int i = 0; i < N; i++) { dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i)); @@ -665,13 +667,17 @@ public class ZenModeHelper { return; } pw.printf("allow(alarms=%b,media=%b,system=%b,calls=%b,callsFrom=%s,repeatCallers=%b," - + "messages=%b,messagesFrom=%s,events=%b,reminders=%b)\n", + + "messages=%b,messagesFrom=%s,conversations=%b,conversationsFrom=%s," + + "events=%b,reminders=%b)\n", config.allowAlarms, config.allowMedia, config.allowSystem, config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom), config.allowRepeatCallers, config.allowMessages, ZenModeConfig.sourceToString(config.allowMessagesFrom), + config.allowConversations, + ZenPolicy.conversationTypeToString(config.allowConversationsFrom), config.allowEvents, config.allowReminders); - pw.printf(" disallow(visualEffects=%s)\n", config.suppressedVisualEffects); + pw.print(prefix); + pw.printf(" disallow(visualEffects=%s)\n", config.suppressedVisualEffects); pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule); if (config.automaticRules.isEmpty()) return; final int N = config.automaticRules.size(); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 40ea6cfb6d4a..b98bb0831b0e 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -234,12 +234,12 @@ public class Installer extends SystemService { } public void moveCompleteApp(String fromUuid, String toUuid, String packageName, - String dataAppName, int appId, String seInfo, int targetSdkVersion) - throws InstallerException { + String dataAppName, int appId, String seInfo, int targetSdkVersion, + String fromCodePath) throws InstallerException { if (!checkBeforeRemote()) return; try { mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo, - targetSdkVersion); + targetSdkVersion, fromCodePath); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 7bb782b9fadc..3e64e9828c3e 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -29,12 +29,14 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageManager; +import android.content.pm.IShortcutChangeCallback; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; @@ -661,8 +663,8 @@ public class LauncherAppsService extends SystemService { @Override public ParceledListSlice getShortcuts(String callingPackage, long changedSince, - String packageName, List shortcutIds, ComponentName componentName, int flags, - UserHandle targetUser) { + String packageName, List shortcutIds, List<LocusId> locusIds, + ComponentName componentName, int flags, UserHandle targetUser) { ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { return new ParceledListSlice<>(Collections.EMPTY_LIST); @@ -671,16 +673,31 @@ public class LauncherAppsService extends SystemService { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); } + if (locusIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by locus ID, package name must also be set"); + } // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below. return new ParceledListSlice<>((List<ShortcutInfo>) mShortcutServiceInternal.getShortcuts(getCallingUserId(), - callingPackage, changedSince, packageName, shortcutIds, + callingPackage, changedSince, packageName, shortcutIds, locusIds, componentName, flags, targetUser.getIdentifier(), injectBinderCallingPid(), injectBinderCallingUid())); } @Override + public void registerShortcutChangeCallback(String callingPackage, long changedSince, + String packageName, List shortcutIds, List<LocusId> locusIds, + ComponentName componentName, int flags, IShortcutChangeCallback callback, + int callbackId) { + } + + @Override + public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) { + } + + @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle targetUser) { ensureShortcutPermission(callingPackage); @@ -1137,7 +1154,7 @@ public class LauncherAppsService extends SystemService { mShortcutServiceInternal.getShortcuts(launcherUserId, cookie.packageName, /* changedSince= */ 0, packageName, /* shortcutIds=*/ null, - /* component= */ null, + /* locusIds=*/ null, /* component= */ null, ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED , userId, cookie.callingPid, cookie.callingUid); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index bf7bebd10a13..0cf8b424b84d 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -129,6 +129,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.security.VerityUtils; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -212,7 +213,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SIGNATURE = "signature"; private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; - private static final int[] EMPTY_CHILD_SESSION_ARRAY = {}; + private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; + private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {}; private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; @@ -375,8 +377,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO(b/146080380): merge file list with Callback installation. private IncrementalFileStorages mIncrementalFileStorages; - private static final String[] EMPTY_STRING_ARRAY = new String[]{}; - private static final FileFilter sAddedApkFilter = new FileFilter() { @Override public boolean accept(File file) { @@ -558,10 +558,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionErrorMessage = stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; - if (isStreamingInstallation() - && this.params.dataLoaderParams.getComponentName().getPackageName() - == SYSTEM_DATA_LOADER_PACKAGE) { - assertShellOrSystemCalling("System data loaders"); + if (isDataLoaderInstallation()) { + if (isApexInstallation()) { + throw new IllegalArgumentException( + "DataLoader installation of APEX modules is not allowed."); + } + } + + if (isStreamingInstallation()) { + if (!isIncrementalInstallationAllowed(mPackageName)) { + throw new IllegalArgumentException( + "Incremental installation of this package is not allowed."); + } + if (this.params.dataLoaderParams.getComponentName().getPackageName() + == SYSTEM_DATA_LOADER_PACKAGE) { + assertShellOrSystemCalling("System data loaders"); + } } } @@ -719,7 +731,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!isDataLoaderInstallation()) { String[] result = stageDir.list(); if (result == null) { - result = EMPTY_STRING_ARRAY; + result = EmptyArray.STRING; } return result; } @@ -1174,6 +1186,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** + * Checks if the package can be installed on IncFs. + */ + private static boolean isIncrementalInstallationAllowed(String packageName) { + final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + final AndroidPackage existingPackage = pmi.getPackage(packageName); + if (existingPackage == null) { + return true; + } + + return !PackageManagerService.isSystemApp(existingPackage); + } + + /** * If this was not already called, the session will be sealed. * * This method may be called multiple times to update the status receiver validate caller @@ -1362,14 +1387,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return false; } - final PackageInfo pkgInfo = mPm.getPackageInfo( - params.appPackageName, PackageManager.GET_SIGNATURES - | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); - if (isApexInstallation()) { validateApexInstallLocked(); } else { - validateApkInstallLocked(pkgInfo); + validateApkInstallLocked(); } } @@ -1786,8 +1807,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * {@link PackageManagerService}. */ @GuardedBy("mLock") - private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo) - throws PackageManagerException { + private void validateApkInstallLocked() throws PackageManagerException { ApkLite baseApk = null; mPackageName = null; mVersionCode = -1; @@ -1797,6 +1817,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedStagedFiles.clear(); mResolvedInheritedFiles.clear(); + final PackageInfo pkgInfo = mPm.getPackageInfo( + params.appPackageName, PackageManager.GET_SIGNATURES + | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); + // Partial installs must be consistent with existing install if (params.mode == SessionParams.MODE_INHERIT_EXISTING && (pkgInfo == null || pkgInfo.applicationInfo == null)) { @@ -3096,7 +3120,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (grantedRuntimePermissions.size() > 0) { - params.grantedRuntimePermissions = (String[]) grantedRuntimePermissions.toArray(); + params.grantedRuntimePermissions = + grantedRuntimePermissions.toArray(EmptyArray.STRING); } if (whitelistedRestrictedPermissions.size() > 0) { @@ -3115,7 +3140,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { FileInfo[] fileInfosArray = null; if (!files.isEmpty()) { - fileInfosArray = (FileInfo[]) files.toArray(); + fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY); } InstallSource installSource = InstallSource.create(installInitiatingPackageName, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 79cf23ff06b1..c85859072d89 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -150,6 +150,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.AuxiliaryResolveInfo; import android.content.pm.ChangedPackages; import android.content.pm.ComponentInfo; +import android.content.pm.DataLoaderType; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; @@ -1537,15 +1538,16 @@ public class PackageManagerService extends IPackageManager.Stub final @NonNull String mRequiredPermissionControllerPackage; final @Nullable String mSetupWizardPackage; final @Nullable String mStorageManagerPackage; - final @Nullable String mSystemTextClassifierPackage; + final @Nullable String mDefaultTextClassifierPackage; + final @Nullable String mSystemTextClassifierPackageName; final @Nullable String mWellbeingPackage; final @Nullable String mDocumenterPackage; final @Nullable String mConfiguratorPackage; final @Nullable String mAppPredictionServicePackage; final @Nullable String mIncidentReportApproverPackage; final @Nullable String[] mTelephonyPackages; - final @NonNull String mServicesExtensionPackageName; - final @NonNull String mSharedSystemSharedLibraryPackageName; + final @Nullable String mServicesExtensionPackageName; + final @Nullable String mSharedSystemSharedLibraryPackageName; final @Nullable String mRetailDemoPackage; private final PackageUsage mPackageUsage = new PackageUsage(); @@ -1657,7 +1659,8 @@ public class PackageManagerService extends IPackageManager.Stub handlePackagePostInstall(parentRes, grantPermissions, killApp, virtualPreload, grantedPermissions, whitelistedRestrictedPermissions, didRestore, - args.installSource.installerPackageName, args.observer); + args.installSource.installerPackageName, args.observer, + args.mDataLoaderType); // Handle the child packages final int childCount = (parentRes.addedChildPackages != null) @@ -1667,7 +1670,8 @@ public class PackageManagerService extends IPackageManager.Stub handlePackagePostInstall(childRes, grantPermissions, killApp, virtualPreload, grantedPermissions, whitelistedRestrictedPermissions, false /*didRestore*/, - args.installSource.installerPackageName, args.observer); + args.installSource.installerPackageName, args.observer, + args.mDataLoaderType); } // Log tracing if needed @@ -1994,7 +1998,7 @@ public class PackageManagerService extends IPackageManager.Stub boolean killApp, boolean virtualPreload, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, boolean launchedForRestore, String installerPackage, - IPackageInstallObserver2 installObserver) { + IPackageInstallObserver2 installObserver, int dataLoaderType) { final boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED; final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null; @@ -2095,11 +2099,14 @@ public class PackageManagerService extends IPackageManager.Stub if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); + // Send to all running apps. sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0 /*flags*/, null /*targetPackage*/, null /*finishedReceiver*/, updateUserIds, instantUserIds); if (installerPackageName != null) { + // Send to the installer, even if it's not running. sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0 /*flags*/, installerPackageName, null /*finishedReceiver*/, @@ -3155,8 +3162,8 @@ public class PackageManagerService extends IPackageManager.Stub mSetupWizardPackage = getSetupWizardPackageNameImpl(); mComponentResolver.fixProtectedFilterPriorities(); - mSystemTextClassifierPackage = getSystemTextClassifierPackageName(); - + mDefaultTextClassifierPackage = getDefaultTextClassifierPackageName(); + mSystemTextClassifierPackageName = getSystemTextClassifierPackageName(); mWellbeingPackage = getWellbeingPackageName(); mDocumenterPackage = getDocumenterPackageName(); mConfiguratorPackage = getDeviceConfiguratorPackageName(); @@ -3351,6 +3358,7 @@ public class PackageManagerService extends IPackageManager.Stub mServicesExtensionPackageName = null; mSharedSystemSharedLibraryPackageName = null; } + // PermissionController hosts default permission granting and role management, so it's a // critical part of the core system. mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(); @@ -13976,9 +13984,11 @@ public class PackageManagerService extends IPackageManager.Stub final int appId; final String seinfo; final int targetSdkVersion; + final String fromCodePath; public MoveInfo(int moveId, String fromUuid, String toUuid, String packageName, - String dataAppName, int appId, String seinfo, int targetSdkVersion) { + String dataAppName, int appId, String seinfo, int targetSdkVersion, + String fromCodePath) { this.moveId = moveId; this.fromUuid = fromUuid; this.toUuid = toUuid; @@ -13987,6 +13997,7 @@ public class PackageManagerService extends IPackageManager.Stub this.appId = appId; this.seinfo = seinfo; this.targetSdkVersion = targetSdkVersion; + this.fromCodePath = fromCodePath; } } @@ -14112,13 +14123,14 @@ public class PackageManagerService extends IPackageManager.Stub MultiPackageInstallParams mParentInstallParams; final long requiredInstalledVersionCode; final boolean forceQueryableOverride; + final int mDataLoaderType; InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer, int installFlags, InstallSource installSource, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, SigningDetails signingDetails, int installReason, - long requiredInstalledVersionCode) { + long requiredInstalledVersionCode, int dataLoaderType) { super(user); this.origin = origin; this.move = move; @@ -14134,40 +14146,42 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; this.requiredInstalledVersionCode = requiredInstalledVersionCode; this.forceQueryableOverride = false; + this.mDataLoaderType = dataLoaderType; } InstallParams(ActiveInstallSession activeInstallSession) { super(activeInstallSession.getUser()); + final PackageInstaller.SessionParams sessionParams = + activeInstallSession.getSessionParams(); if (DEBUG_INSTANT) { - if ((activeInstallSession.getSessionParams().installFlags + if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName()); } } verificationInfo = new VerificationInfo( - activeInstallSession.getSessionParams().originatingUri, - activeInstallSession.getSessionParams().referrerUri, - activeInstallSession.getSessionParams().originatingUid, + sessionParams.originatingUri, + sessionParams.referrerUri, + sessionParams.originatingUid, activeInstallSession.getInstallerUid()); origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir()); move = null; installReason = fixUpInstallReason( activeInstallSession.getInstallSource().installerPackageName, activeInstallSession.getInstallerUid(), - activeInstallSession.getSessionParams().installReason); + sessionParams.installReason); observer = activeInstallSession.getObserver(); - installFlags = activeInstallSession.getSessionParams().installFlags; + installFlags = sessionParams.installFlags; installSource = activeInstallSession.getInstallSource(); - volumeUuid = activeInstallSession.getSessionParams().volumeUuid; - packageAbiOverride = activeInstallSession.getSessionParams().abiOverride; - grantedRuntimePermissions = activeInstallSession.getSessionParams() - .grantedRuntimePermissions; - whitelistedRestrictedPermissions = activeInstallSession.getSessionParams() - .whitelistedRestrictedPermissions; + volumeUuid = sessionParams.volumeUuid; + packageAbiOverride = sessionParams.abiOverride; + grantedRuntimePermissions = sessionParams.grantedRuntimePermissions; + whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions; signingDetails = activeInstallSession.getSigningDetails(); - requiredInstalledVersionCode = activeInstallSession.getSessionParams() - .requiredInstalledVersionCode; - forceQueryableOverride = activeInstallSession.getSessionParams().forceQueryableOverride; + requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode; + forceQueryableOverride = sessionParams.forceQueryableOverride; + mDataLoaderType = (sessionParams.dataLoaderParams != null) + ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE; } @Override @@ -14770,6 +14784,7 @@ public class PackageManagerService extends IPackageManager.Stub final int installReason; final boolean forceQueryableOverride; @Nullable final MultiPackageInstallParams mMultiPackageInstallParams; + final int mDataLoaderType; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this @@ -14783,7 +14798,7 @@ public class PackageManagerService extends IPackageManager.Stub List<String> whitelistedRestrictedPermissions, String traceMethod, int traceCookie, SigningDetails signingDetails, int installReason, boolean forceQueryableOverride, - MultiPackageInstallParams multiPackageInstallParams) { + MultiPackageInstallParams multiPackageInstallParams, int dataLoaderType) { this.origin = origin; this.move = move; this.installFlags = installFlags; @@ -14801,6 +14816,7 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; this.forceQueryableOverride = forceQueryableOverride; this.mMultiPackageInstallParams = multiPackageInstallParams; + this.mDataLoaderType = dataLoaderType; } /** New install */ @@ -14810,7 +14826,8 @@ public class PackageManagerService extends IPackageManager.Stub params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions, params.traceMethod, params.traceCookie, params.signingDetails, - params.installReason, params.forceQueryableOverride, params.mParentInstallParams); + params.installReason, params.forceQueryableOverride, + params.mParentInstallParams, params.mDataLoaderType); } abstract int copyApk(); @@ -14901,7 +14918,8 @@ public class PackageManagerService extends IPackageManager.Stub super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, null, null, instructionSets, null, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN, - PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */); + PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */, + DataLoaderType.NONE); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; } @@ -14981,9 +14999,7 @@ public class PackageManagerService extends IPackageManager.Stub try { makeDirRecursive(afterCodeFile.getParentFile(), 0775); if (onIncremental) { - // TODO(b/147371381): fix incremental installation - mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(), - afterCodeFile.getAbsolutePath()); + mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile); } else { Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); } @@ -15102,7 +15118,8 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mInstaller) { try { mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName, - move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion); + move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion, + move.fromCodePath); } catch (InstallerException e) { Slog.w(TAG, "Failed to move app", e); return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; @@ -19626,15 +19643,15 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public String getSystemTextClassifierPackageName() { - return ensureSystemPackageName(mContext.getString( - R.string.config_defaultTextClassifierPackage)); + public String getDefaultTextClassifierPackageName() { + return ensureSystemPackageName( + mContext.getString(R.string.config_servicesExtensionPackage)); } @Override - public String[] getSystemTextClassifierPackages() { - return ensureSystemPackageNames(mContext.getResources().getStringArray( - R.array.config_defaultTextClassifierPackages)); + public String getSystemTextClassifierPackageName() { + return ensureSystemPackageName( + mContext.getString(R.string.config_defaultTextClassifierPackage)); } @Override @@ -22052,6 +22069,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageFreezer freezer; final int[] installedUserIds; final boolean isCurrentLocationExternal; + final String fromCodePath; // reader synchronized (mLock) { @@ -22108,6 +22126,7 @@ public class PackageManagerService extends IPackageManager.Stub targetSdkVersion = pkg.getTargetSdkVersion(); freezer = freezePackage(packageName, "movePackageInternal"); installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true); + fromCodePath = pkg.getCodePath(); } final Bundle extras = new Bundle(); @@ -22236,7 +22255,7 @@ public class PackageManagerService extends IPackageManager.Stub final String dataAppName = codeFile.getName(); move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName, - dataAppName, appId, seinfo, targetSdkVersion); + dataAppName, appId, seinfo, targetSdkVersion, fromCodePath); } else { move = null; } @@ -22249,7 +22268,8 @@ public class PackageManagerService extends IPackageManager.Stub installSource, volumeUuid, null /*verificationInfo*/, user, packageAbiOverride, null /*grantedPermissions*/, null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN, - PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST); + PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST, + DataLoaderType.NONE); params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -22755,6 +22775,11 @@ public class PackageManagerService extends IPackageManager.Stub private class PackageManagerNative extends IPackageManagerNative.Stub { @Override + public String[] getAllPackages() { + return PackageManagerService.this.getAllPackages().toArray(new String[0]); + } + + @Override public String[] getNamesForUids(int[] uids) throws RemoteException { final String[] results = PackageManagerService.this.getNamesForUids(uids); // massage results so they can be parsed by the native binder @@ -23063,7 +23088,7 @@ public class PackageManagerService extends IPackageManager.Stub } private String[] getKnownPackageNamesInternal(int knownPackage, int userId) { - switch(knownPackage) { + switch (knownPackage) { case PackageManagerInternal.PACKAGE_BROWSER: return new String[]{mPermissionManager.getDefaultBrowser(userId)}; case PackageManagerInternal.PACKAGE_INSTALLER: @@ -23075,7 +23100,8 @@ public class PackageManagerService extends IPackageManager.Stub case PackageManagerInternal.PACKAGE_VERIFIER: return filterOnlySystemPackages(mRequiredVerifierPackage); case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER: - return filterOnlySystemPackages(mSystemTextClassifierPackage); + return filterOnlySystemPackages( + mDefaultTextClassifierPackage, mSystemTextClassifierPackageName); case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER: return filterOnlySystemPackages(mRequiredPermissionControllerPackage); case PackageManagerInternal.PACKAGE_WELLBEING: diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index f7889ea6141c..d16c0748ef0e 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -1985,7 +1986,6 @@ public class ShortcutService extends IShortcutService.Stub { // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. verifyShortcutInfoPackage(callingPackage, shortcut); } - final String shortcutPackage = shortcut.getPackage(); final boolean ret; synchronized (mLock) { @@ -1999,6 +1999,7 @@ public class ShortcutService extends IShortcutService.Stub { // someone already), then we just replace the existing one with this new one, // and then proceed the rest of the process. if (shortcut != null) { + final String shortcutPackage = shortcut.getPackage(); final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( shortcutPackage, userId); final String id = shortcut.getId(); @@ -2155,48 +2156,6 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isDynamicVisible); - } - } - - @Override - public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isManifestVisible); - } - } - - @Override - public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isPinnedVisible); - } - } - - @Override public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName, @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { verifyCaller(packageName, userId); @@ -2631,7 +2590,7 @@ public class ShortcutService extends IShortcutService.Stub { public List<ShortcutInfo> getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, - @Nullable ComponentName componentName, + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, int queryFlags, int userId, int callingPid, int callingUid) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(); @@ -2652,15 +2611,16 @@ public class ShortcutService extends IShortcutService.Stub { if (packageName != null) { getShortcutsInnerLocked(launcherUserId, - callingPackage, packageName, shortcutIds, changedSince, + callingPackage, packageName, shortcutIds, locusIds, changedSince, componentName, queryFlags, userId, ret, cloneFlag, callingPid, callingUid); } else { final List<String> shortcutIdsF = shortcutIds; + final List<LocusId> locusIdsF = locusIds; getUserShortcutsLocked(userId).forAllPackages(p -> { getShortcutsInnerLocked(launcherUserId, - callingPackage, p.getPackageName(), shortcutIdsF, changedSince, - componentName, queryFlags, userId, ret, cloneFlag, + callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF, + changedSince, componentName, queryFlags, userId, ret, cloneFlag, callingPid, callingUid); }); } @@ -2670,12 +2630,15 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("ShortcutService.this.mLock") private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, - @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, + @Nullable String packageName, @Nullable List<String> shortcutIds, + @Nullable List<LocusId> locusIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, int callingPid, int callingUid) { final ArraySet<String> ids = shortcutIds == null ? null : new ArraySet<>(shortcutIds); + final ArraySet<LocusId> locIds = locusIds == null ? null + : new ArraySet<>(locusIds); final ShortcutUser user = getUserShortcutsLocked(userId); final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); @@ -2702,6 +2665,9 @@ public class ShortcutService extends IShortcutService.Stub { if (ids != null && !ids.contains(si.getId())) { return false; } + if (locIds != null && !locIds.contains(si.getLocusId())) { + return false; + } if (componentName != null) { if (si.getActivity() != null && !si.getActivity().equals(componentName)) { diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 46893b25de9a..e6eaf211a86a 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -720,12 +720,9 @@ public final class DefaultPermissionGrantPolicy { userId, STORAGE_PERMISSIONS); // TextClassifier Service - final String[] packages = mContext.getPackageManager().getSystemTextClassifierPackages(); - if (packages.length > 0) { - // We have a list of supported system TextClassifier package names, the first one - // package is the default system TextClassifier service. Grant permissions to default - // TextClassifier Service. - grantPermissionsToSystemPackage(packages[0], userId, + for (String textClassifierPackage : + getKnownPackages(PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER, userId)) { + grantPermissionsToSystemPackage(textClassifierPackage, userId, COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS); } diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index 6daf5162ebad..6eba59acbc94 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -402,7 +402,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn public String getStatus() { return mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID); } } diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index bf7413b64588..4d7af9cc0d44 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -93,19 +93,6 @@ class Rollback { */ static final int ROLLBACK_STATE_DELETED = 4; - @IntDef(flag = true, prefix = { "MATCH_" }, value = { - MATCH_APK_IN_APEX, - }) - @Retention(RetentionPolicy.SOURCE) - @interface RollbackInfoFlags {} - - /** - * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex. - * These packages do not have their own sessions. They are embedded in an apex which has a - * session id. - */ - static final int MATCH_APK_IN_APEX = 1; - /** * The session ID for the staged session if this rollback data represents a staged session, * {@code -1} otherwise. @@ -783,33 +770,6 @@ class Rollback { } /** - * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback} - * instance. By default, this method does not include apk-in-apex package in the count. - * - * @param flags Apk-in-apex packages can be included in the count by passing - * {@link Rollback#MATCH_APK_IN_APEX} - * - * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback} - * according to {@code flags} passed - */ - int getPackageCount(@RollbackInfoFlags int flags) { - synchronized (mLock) { - List<PackageRollbackInfo> packages = info.getPackages(); - if ((flags & MATCH_APK_IN_APEX) != 0) { - return packages.size(); - } - - int packagesWithoutApkInApex = 0; - for (PackageRollbackInfo rollbackInfo : packages) { - if (!rollbackInfo.isApkInApex()) { - packagesWithoutApkInApex++; - } - } - return packagesWithoutApkInApex; - } - } - - /** * Adds a rollback token to be associated with this rollback. This may be used to * identify which rollback should be removed in case {@link PackageManager} sends an * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent. @@ -842,13 +802,6 @@ class Rollback { } /** - * Returns the number of package session ids in this rollback. - */ - int getPackageSessionIdCount() { - return mPackageSessionIds.length; - } - - /** * Called when a child session finished with success. * Returns true when all child sessions are notified with success. This rollback will be * enabled only after all child sessions finished with success. @@ -859,6 +812,23 @@ class Rollback { } } + /** + * Returns true if all packages in this rollback are enabled. We won't enable this rollback + * until all packages are enabled. Note we don't count apk-in-apex here since they are enabled + * automatically when the embedding apex is enabled. + */ + boolean allPackagesEnabled() { + synchronized (mLock) { + int packagesWithoutApkInApex = 0; + for (PackageRollbackInfo rollbackInfo : info.getPackages()) { + if (!rollbackInfo.isApkInApex()) { + packagesWithoutApkInApex++; + } + } + return packagesWithoutApkInApex == mPackageSessionIds.length; + } + } + 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 3fa114e3b7a3..8bd9533727d6 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1237,8 +1237,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // equal to the number of sessions we are installing, to ensure we didn't skip enabling // of any sessions. If we successfully enable an apex, then we can assume we enabled // rollback for the embedded apk-in-apex, if any. - // TODO: add a helper instead of exposing 2 methods from Rollback - if (rollback.getPackageCount(0 /*flags*/) != rollback.getPackageSessionIdCount()) { + if (!rollback.allPackagesEnabled()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); rollback.delete(mAppDataRollbackHelper); return null; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 4f894821db27..7b046c1aa3a6 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -199,11 +199,6 @@ class RollbackStore { * Creates a new Rollback instance for a non-staged rollback with * backupDir assigned. */ - Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName) { - File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); - return new Rollback(rollbackId, backupDir, -1, userId, installerPackageName); - } - Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName, int[] packageSessionIds) { File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); @@ -216,16 +211,6 @@ class RollbackStore { * backupDir assigned. */ Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId, - String installerPackageName) { - File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); - return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName); - } - - /** - * TODO: Now we have 4 factory methods for creating Rollback objects which is verbose and - * cumbersome. Need to merge them for simplicity. - */ - Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId, String installerPackageName, int[] packageSessionIds) { File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 96f1219861ec..5c79f6e6391d 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -2480,7 +2480,7 @@ public class StatsPullAtomService extends SystemService { .writeString(Build.BRAND) .writeString(Build.PRODUCT) .writeString(Build.DEVICE) - .writeString(Build.VERSION.RELEASE) + .writeString(Build.VERSION.RELEASE_OR_CODENAME) .writeString(Build.ID) .writeString(Build.VERSION.INCREMENTAL) .writeString(Build.TYPE) diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 3dee853240a6..34d2c16ed0ac 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.ArrayMap; +import android.util.LruCache; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; @@ -63,7 +63,8 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.Queue; @@ -132,9 +133,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mManagerService.mLock) { UserState userState = mManagerService.peekUserStateLocked(userId); if (userState != null) { - if (userState.mConnection != null) { - userState.mConnection.cleanupService(); - } + userState.cleanupServiceLocked(); mManagerService.mUserStates.remove(userId); } } @@ -147,22 +146,31 @@ 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 Map<TextClassificationSessionId, Integer> mSessionUserIds = new ArrayMap<>(); - @GuardedBy("mLock") - private TextClassificationConstants mSettings; + private final LruCache<TextClassificationSessionId, TextClassificationContext> + mSessionContextCache = new LruCache<>(40); + private final TextClassificationConstants mSettings; + @Nullable + private final String mDefaultTextClassifierPackage; + @Nullable + private final String mSystemTextClassifierPackage; private TextClassificationManagerService(Context context) { mContext = Objects.requireNonNull(context); mLock = new Object(); + mSettings = new TextClassificationConstants(); mSettingsListener = new TextClassifierSettingsListener(mContext); + PackageManager packageManager = mContext.getPackageManager(); + mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName(); + mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName(); } private void startListenSettings() { mSettingsListener.registerObserver(); } - @Override public void onConnectedStateChanged(@ConnectionState int connected) { } @@ -178,6 +186,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.getUserId(), request.getCallingPackageName(), /* attemptToBind= */ true, + request.getUseDefaultTextClassifier(), service -> service.onSuggestSelection(sessionId, request, callback), "onSuggestSelection", callback); @@ -194,6 +203,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.getUserId(), request.getCallingPackageName(), /* attemptToBind= */ true, + request.getUseDefaultTextClassifier(), service -> service.onClassifyText(sessionId, request, callback), "onClassifyText", callback); @@ -210,6 +220,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.getUserId(), request.getCallingPackageName(), /* attemptToBind= */ true, + request.getUseDefaultTextClassifier(), service -> service.onGenerateLinks(sessionId, request, callback), "onGenerateLinks", callback); @@ -223,28 +234,32 @@ public final class TextClassificationManagerService extends ITextClassifierServi handleRequest( event.getUserId(), - event.getPackageName(), + /* callingPackageName= */ null, /* attemptToBind= */ false, + event.getUseDefaultTextClassifier(), service -> service.onSelectionEvent(sessionId, event), "onSelectionEvent", NO_OP_CALLBACK); } + @Override public void onTextClassifierEvent( @Nullable TextClassificationSessionId sessionId, TextClassifierEvent event) throws RemoteException { Objects.requireNonNull(event); - final String packageName = event.getEventContext() == null - ? null - : event.getEventContext().getPackageName(); final int userId = event.getEventContext() == null ? UserHandle.getCallingUserId() : event.getEventContext().getUserId(); + final boolean useDefaultTextClassifier = + event.getEventContext() != null + ? event.getEventContext().getUseDefaultTextClassifier() + : true; handleRequest( userId, - packageName, + /* callingPackageName= */ null, /* attemptToBind= */ false, + useDefaultTextClassifier, service -> service.onTextClassifierEvent(sessionId, event), "onTextClassifierEvent", NO_OP_CALLBACK); @@ -261,6 +276,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.getUserId(), request.getCallingPackageName(), /* attemptToBind= */ true, + request.getUseDefaultTextClassifier(), service -> service.onDetectLanguage(sessionId, request, callback), "onDetectLanguage", callback); @@ -277,6 +293,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi request.getUserId(), request.getCallingPackageName(), /* attemptToBind= */ true, + request.getUseDefaultTextClassifier(), service -> service.onSuggestConversationActions(sessionId, request, callback), "onSuggestConversationActions", callback); @@ -294,9 +311,10 @@ public final class TextClassificationManagerService extends ITextClassifierServi userId, classificationContext.getPackageName(), /* attemptToBind= */ false, + classificationContext.getUseDefaultTextClassifier(), service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); - mSessionUserIds.put(sessionId, userId); + mSessionContextCache.put(sessionId, classificationContext); }, "onCreateTextClassificationSession", NO_OP_CALLBACK); @@ -308,16 +326,23 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(sessionId); synchronized (mLock) { - final int userId = mSessionUserIds.containsKey(sessionId) - ? mSessionUserIds.get(sessionId) + TextClassificationContext textClassificationContext = + mSessionContextCache.get(sessionId); + final int userId = textClassificationContext != null + ? textClassificationContext.getUserId() : UserHandle.getCallingUserId(); + final boolean useDefaultTextClassifier = + textClassificationContext != null + ? textClassificationContext.getUseDefaultTextClassifier() + : true; handleRequest( userId, /* callingPackageName= */ null, /* attemptToBind= */ false, + useDefaultTextClassifier, service -> { service.onDestroyTextClassificationSession(sessionId); - mSessionUserIds.remove(sessionId); + mSessionContextCache.remove(sessionId); }, "onDestroyTextClassificationSession", NO_OP_CALLBACK); @@ -328,7 +353,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private UserState getUserStateLocked(int userId) { UserState result = mUserStates.get(userId); if (result == null) { - result = new UserState(userId, mContext, mLock); + result = new UserState(userId); mUserStates.put(userId, result); } return result; @@ -339,6 +364,19 @@ public final class TextClassificationManagerService extends ITextClassifierServi return mUserStates.get(userId); } + private int resolvePackageToUid(@Nullable String packageName, @UserIdInt int userId) { + if (packageName == null) { + return Process.INVALID_UID; + } + final PackageManager pm = mContext.getPackageManager(); + try { + return pm.getPackageUidAsUser(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "Could not get the UID for " + packageName); + } + return Process.INVALID_UID; + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return; @@ -353,18 +391,25 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("context", mContext); pw.println(); + pw.printPair("defaultTextClassifierPackage", mDefaultTextClassifierPackage); + pw.println(); + pw.printPair("systemTextClassifierPackage", mSystemTextClassifierPackage); + pw.println(); synchronized (mLock) { int size = mUserStates.size(); - pw.print("Number user states: "); pw.println(size); + pw.print("Number user states: "); + pw.println(size); if (size > 0) { for (int i = 0; i < size; i++) { pw.increaseIndent(); UserState userState = mUserStates.valueAt(i); - pw.print(i); pw.print(":"); userState.dump(pw); pw.println(); + pw.printPair("User", mUserStates.keyAt(i)); + pw.println(); + userState.dump(pw); pw.decreaseIndent(); } } - pw.println("Number of active sessions: " + mSessionUserIds.size()); + pw.println("Number of active sessions: " + mSessionContextCache.size()); } } @@ -372,57 +417,55 @@ public final class TextClassificationManagerService extends ITextClassifierServi @UserIdInt int userId, @Nullable String callingPackageName, boolean attemptToBind, + boolean useDefaultTextClassifier, @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer, @NonNull String methodName, - @NonNull ITextClassifierCallback callback) - throws RemoteException { + @NonNull ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(textClassifierServiceConsumer); Objects.requireNonNull(methodName); Objects.requireNonNull(callback); - validateInput(mContext, callingPackageName, userId); + try { + validateCallingPackage(callingPackageName); + validateUser(userId); + } catch (Exception e) { + throw new RemoteException("Invalid request: " + e.getMessage(), e, + /* enableSuppression */ true, /* writableStackTrace */ true); + } synchronized (mLock) { UserState userState = getUserStateLocked(userId); - if (attemptToBind && !userState.bindLocked()) { + ServiceState serviceState = + userState.getServiceStateLocked(useDefaultTextClassifier); + if (serviceState == null) { + Slog.d(LOG_TAG, "No configured system TextClassifierService"); + callback.onFailure(); + } else if (attemptToBind && !serviceState.bindLocked()) { Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName); callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) { + } else if (serviceState.isBoundLocked()) { + if (!serviceState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) { return; } - textClassifierServiceConsumer.accept(userState.mService); + textClassifierServiceConsumer.accept(serviceState.mService); } else { - userState.mPendingRequests.add( + serviceState.mPendingRequests.add( new PendingRequest( methodName, - () -> textClassifierServiceConsumer.accept(userState.mService), + () -> textClassifierServiceConsumer.accept(serviceState.mService), callback::onFailure, callback.asBinder(), this, - userState, + serviceState, Binder.getCallingUid())); } } } - private void unbindServiceIfNecessary() { - final ComponentName serviceComponentName = - TextClassifierService.getServiceComponentName(mContext); - if (serviceComponentName == null) { - // It should not occur if we had defined default service names in config.xml - Slog.w(LOG_TAG, "No default configured system TextClassifierService."); - return; - } + private void onTextClassifierServicePackageOverrideChanged(String overriddenPackage) { synchronized (mLock) { final int size = mUserStates.size(); for (int i = 0; i < size; i++) { UserState userState = mUserStates.valueAt(i); - // Only unbind for a new service - if (userState.isCurrentlyBoundToComponentLocked(serviceComponentName)) { - return; - } - if (userState.isBoundLocked()) { - userState.unbindLocked(); - } + userState.onTextClassifierServicePackageOverrideChangedLocked(overriddenPackage); } } } @@ -430,27 +473,35 @@ public final class TextClassificationManagerService extends ITextClassifierServi private static final class PendingRequest implements IBinder.DeathRecipient { private final int mUid; - @Nullable private final String mName; - @Nullable private final IBinder mBinder; - @NonNull private final Runnable mRequest; - @Nullable private final Runnable mOnServiceFailure; + @Nullable + private final String mName; + @Nullable + private final IBinder mBinder; + @NonNull + private final Runnable mRequest; + @Nullable + private final Runnable mOnServiceFailure; @GuardedBy("mLock") - @NonNull private final UserState mOwningUser; - @NonNull private final TextClassificationManagerService mService; + @NonNull + private final ServiceState mServiceState; + @NonNull + private final TextClassificationManagerService mService; /** * Initializes a new pending request. - * @param request action to perform when the service is bound + * + * @param request action to perform when the service is bound * @param onServiceFailure action to perform when the service dies or disconnects - * @param binder binder to the process that made this pending request - * @param service - * @param owningUser + * @param binder binder to the process that made this pending request + * @parm service the TCMS instance. + * @param serviceState the service state of the service that will execute the request. + * @param uid the calling uid of the request. */ PendingRequest(@Nullable String name, @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure, @Nullable IBinder binder, - TextClassificationManagerService service, - UserState owningUser, int uid) { + @NonNull TextClassificationManagerService service, + @NonNull ServiceState serviceState, int uid) { mName = name; mRequest = logOnFailure(Objects.requireNonNull(request), "handling pending request"); @@ -458,7 +509,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi logOnFailure(onServiceFailure, "notifying callback of service failure"); mBinder = binder; mService = service; - mOwningUser = owningUser; + mServiceState = Objects.requireNonNull(serviceState); if (mBinder != null) { try { mBinder.linkToDeath(this, 0); @@ -479,7 +530,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") private void removeLocked() { - mOwningUser.mPendingRequests.remove(this); + mServiceState.mPendingRequests.remove(this); if (mBinder != null) { mBinder.unlinkToDeath(this, 0); } @@ -492,59 +543,172 @@ public final class TextClassificationManagerService extends ITextClassifierServi e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage())); } - private static void validateInput( - Context context, @Nullable String packageName, @UserIdInt int userId) - throws RemoteException { + private void validateCallingPackage(@Nullable String callingPackage) + throws PackageManager.NameNotFoundException { + if (callingPackage != null) { + final int packageUid = mContext.getPackageManager() + .getPackageUidAsUser(callingPackage, UserHandle.getCallingUserId()); + final int callingUid = Binder.getCallingUid(); + Preconditions.checkArgument( + callingUid == packageUid + // Trust the system process: + || callingUid == android.os.Process.SYSTEM_UID, + "Invalid package name. callingPackage=" + callingPackage + + ", callingUid=" + callingUid); + } + } - try { - if (packageName != null) { - final int packageUid = context.getPackageManager() - .getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); - final int callingUid = Binder.getCallingUid(); - Preconditions.checkArgument(callingUid == packageUid - // Trust the system process: - || callingUid == android.os.Process.SYSTEM_UID, - "Invalid package name. Package=" + packageName - + ", CallingUid=" + callingUid); - } - - Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId"); - final int callingUserId = UserHandle.getCallingUserId(); - if (callingUserId != userId) { - context.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId); - } - } catch (Exception e) { - throw new RemoteException("Invalid request: " + e.getMessage(), e, - /* enableSuppression */ true, /* writableStackTrace */ true); + private void validateUser(@UserIdInt int userId) { + Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId"); + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != userId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId); } } private final class UserState { - @UserIdInt final int mUserId; + @UserIdInt + final int mUserId; + @Nullable + private final ServiceState mDefaultServiceState; + @Nullable + private final ServiceState mSystemServiceState; + @GuardedBy("mLock") + @Nullable + private ServiceState mUntrustedServiceState; + + private UserState(int userId) { + mUserId = userId; + mDefaultServiceState = TextUtils.isEmpty(mDefaultTextClassifierPackage) + ? null + : new ServiceState(userId, mDefaultTextClassifierPackage, /* isTrusted= */true); + mSystemServiceState = TextUtils.isEmpty(mSystemTextClassifierPackage) + ? null + : new ServiceState(userId, mSystemTextClassifierPackage, /* isTrusted= */ true); + } + + @GuardedBy("mLock") + @Nullable + ServiceState getServiceStateLocked(boolean useDefaultTextClassifier) { + if (useDefaultTextClassifier) { + return mDefaultServiceState; + } + String textClassifierServicePackageOverride = + mSettings.getTextClassifierServicePackageOverride(); + if (!TextUtils.isEmpty(textClassifierServicePackageOverride)) { + if (textClassifierServicePackageOverride.equals(mDefaultTextClassifierPackage)) { + return mDefaultServiceState; + } + if (textClassifierServicePackageOverride.equals(mSystemTextClassifierPackage) + && mSystemServiceState != null) { + return mSystemServiceState; + } + if (mUntrustedServiceState == null) { + mUntrustedServiceState = + new ServiceState( + mUserId, + textClassifierServicePackageOverride, + /* isTrusted= */false); + } + return mUntrustedServiceState; + } + return mSystemServiceState != null ? mSystemServiceState : mDefaultServiceState; + } + + @GuardedBy("mLock") + void onTextClassifierServicePackageOverrideChangedLocked(String overriddenPackageName) { + // The override config is just used for testing, and the flag value is not expected + // to change often. So, let's keep it simple and just unbind all the services here. The + // right service will be bound when the next request comes. + for (ServiceState serviceState : getAllServiceStatesLocked()) { + serviceState.unbindIfBoundLocked(); + } + mUntrustedServiceState = null; + } + @GuardedBy("mLock") - TextClassifierServiceConnection mConnection = null; + void bindIfHasPendingRequestsLocked() { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + serviceState.bindIfHasPendingRequestsLocked(); + } + } + + @GuardedBy("mLock") + void cleanupServiceLocked() { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + if (serviceState.mConnection != null) { + serviceState.mConnection.cleanupService(); + } + } + } + + @GuardedBy("mLock") + @NonNull + private List<ServiceState> getAllServiceStatesLocked() { + List<ServiceState> serviceStates = new ArrayList<>(); + if (mDefaultServiceState != null) { + serviceStates.add(mDefaultServiceState); + } + if (mSystemServiceState != null) { + serviceStates.add(mSystemServiceState); + } + if (mUntrustedServiceState != null) { + serviceStates.add(mUntrustedServiceState); + } + return serviceStates; + } + + void dump(IndentingPrintWriter pw) { + synchronized (mLock) { + pw.increaseIndent(); + dump(pw, mDefaultServiceState, "Default"); + dump(pw, mSystemServiceState, "System"); + dump(pw, mUntrustedServiceState, "Untrusted"); + pw.decreaseIndent(); + } + } + + private void dump( + IndentingPrintWriter pw, @Nullable ServiceState serviceState, String name) { + synchronized (mLock) { + if (serviceState != null) { + pw.print(name + ": "); + serviceState.dump(pw); + pw.println(); + } + } + } + } + + private final class ServiceState { + @UserIdInt + final int mUserId; + @NonNull + final String mPackageName; + @NonNull + final TextClassifierServiceConnection mConnection; + final boolean mIsTrusted; + @NonNull @GuardedBy("mLock") final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>(); + @Nullable @GuardedBy("mLock") ITextClassifierService mService; @GuardedBy("mLock") boolean mBinding; + @Nullable @GuardedBy("mLock") ComponentName mBoundComponentName = null; @GuardedBy("mLock") - boolean mBoundToDefaultTrustService; - @GuardedBy("mLock") - int mBoundServiceUid; - - private final Context mContext; - private final Object mLock; + int mBoundServiceUid = Process.INVALID_UID; - private UserState(int userId, Context context, Object lock) { + private ServiceState(@UserIdInt int userId, String packageName, boolean isTrusted) { mUserId = userId; - mContext = Objects.requireNonNull(context); - mLock = Objects.requireNonNull(lock); + mPackageName = packageName; + mConnection = new TextClassifierServiceConnection(mUserId); + mIsTrusted = isTrusted; } @GuardedBy("mLock") @@ -581,18 +745,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi } @GuardedBy("mLock") - private boolean isCurrentlyBoundToComponentLocked(@NonNull ComponentName componentName) { - return (mBoundComponentName != null - && mBoundComponentName.getPackageName().equals( - componentName.getPackageName())); - } - - @GuardedBy("mLock") - private void unbindLocked() { - Slog.v(LOG_TAG, "unbinding from " + mBoundComponentName + " for " + mUserId); - mContext.unbindService(mConnection); - mConnection.cleanupService(); - mConnection = null; + void unbindIfBoundLocked() { + if (isBoundLocked()) { + Slog.v(LOG_TAG, "Unbinding " + mBoundComponentName + " for " + mUserId); + mContext.unbindService(mConnection); + mConnection.cleanupService(); + } } /** @@ -609,8 +767,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi final boolean willBind; final long identity = Binder.clearCallingIdentity(); try { - final ComponentName componentName = - TextClassifierService.getServiceComponentName(mContext); + final ComponentName componentName = getTextClassifierServiceComponent(); if (componentName == null) { // Might happen if the storage is encrypted and the user is not unlocked return false; @@ -618,7 +775,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi Intent serviceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE) .setComponent(componentName); Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent()); - mConnection = new TextClassifierServiceConnection(mUserId); willBind = mContext.bindServiceAsUser( serviceIntent, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE @@ -631,12 +787,21 @@ public final class TextClassificationManagerService extends ITextClassifierServi return willBind; } + @Nullable + private ComponentName getTextClassifierServiceComponent() { + return TextClassifierService.getServiceComponentName( + mContext, + mPackageName, + mIsTrusted ? PackageManager.MATCH_SYSTEM_ONLY : 0); + } + private void dump(IndentingPrintWriter pw) { pw.printPair("context", mContext); pw.printPair("userId", mUserId); synchronized (mLock) { + pw.printPair("packageName", mPackageName); pw.printPair("boundComponentName", mBoundComponentName); - pw.printPair("boundToDefaultTrustService", mBoundToDefaultTrustService); + pw.printPair("isTrusted", mIsTrusted); pw.printPair("boundServiceUid", mBoundServiceUid); pw.printPair("binding", mBinding); pw.printPair("numberRequests", mPendingRequests.size()); @@ -645,7 +810,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") private boolean checkRequestAcceptedLocked(int requestUid, @NonNull String methodName) { - if (mBoundToDefaultTrustService || (requestUid == mBoundServiceUid)) { + if (mIsTrusted || (requestUid == mBoundServiceUid)) { return true; } Slog.w(LOG_TAG, String.format( @@ -654,47 +819,19 @@ public final class TextClassificationManagerService extends ITextClassifierServi return false; } - private boolean isDefaultTrustService(@NonNull ComponentName currentService) { - final String[] defaultServiceNames = - mContext.getPackageManager().getSystemTextClassifierPackages(); - final String servicePackageName = currentService.getPackageName(); - - for (int i = 0; i < defaultServiceNames.length; i++) { - if (defaultServiceNames[i].equals(servicePackageName)) { - return true; - } - } - return false; - } - - private int getServiceUid(@Nullable ComponentName service, int userId) { - if (service == null) { - return Process.INVALID_UID; - } - final String servicePackageName = service.getPackageName(); - final PackageManager pm = mContext.getPackageManager(); - final int serviceUid; - - try { - serviceUid = pm.getPackageUidAsUser(servicePackageName, userId); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(LOG_TAG, "Could not verify UID for " + service); - return Process.INVALID_UID; - } - return serviceUid; - } - @GuardedBy("mLock") private void updateServiceInfoLocked(int userId, @Nullable ComponentName componentName) { mBoundComponentName = componentName; - mBoundToDefaultTrustService = (mBoundComponentName != null && isDefaultTrustService( - mBoundComponentName)); - mBoundServiceUid = getServiceUid(mBoundComponentName, userId); + mBoundServiceUid = + mBoundComponentName == null + ? Process.INVALID_UID + : resolvePackageToUid(mBoundComponentName.getPackageName(), userId); } private final class TextClassifierServiceConnection implements ServiceConnection { - @UserIdInt private final int mUserId; + @UserIdInt + private final int mUserId; TextClassifierServiceConnection(int userId) { mUserId = userId; @@ -745,18 +882,18 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final class TextClassifierSettingsListener implements DeviceConfig.OnPropertiesChangedListener { + @NonNull + private final Context mContext; + @Nullable + private String mServicePackageOverride; - @NonNull private final Context mContext; - @NonNull private final TextClassificationConstants mSettings; - @Nullable private String mServicePackageName; TextClassifierSettingsListener(Context context) { mContext = context; - mSettings = TextClassificationManager.getSettings(mContext); - mServicePackageName = mSettings.getTextClassifierServicePackageOverride(); + mServicePackageOverride = mSettings.getTextClassifierServicePackageOverride(); } - public void registerObserver() { + void registerObserver() { DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_TEXTCLASSIFIER, mContext.getMainExecutor(), @@ -765,13 +902,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { - final String overrideServiceName = mSettings.getTextClassifierServicePackageOverride(); - - if (TextUtils.equals(overrideServiceName, mServicePackageName)) { + final String currentServicePackageOverride = + mSettings.getTextClassifierServicePackageOverride(); + if (TextUtils.equals(currentServicePackageOverride, mServicePackageOverride)) { return; } - mServicePackageName = overrideServiceName; - unbindServiceIfNecessary(); + mServicePackageOverride = currentServicePackageOverride; + onTextClassifierServicePackageOverrideChanged(currentServicePackageOverride); } } } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index b7d63609cff9..ed6424cb4a30 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -37,6 +37,9 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +/** + * The implementation of ITimeDetectorService.aidl. + */ public final class TimeDetectorService extends ITimeDetectorService.Stub { private static final String TAG = "TimeDetectorService"; @@ -75,7 +78,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(handler) { public void onChange(boolean selfChange) { - timeDetectorService.handleAutoTimeDetectionToggle(); + timeDetectorService.handleAutoTimeDetectionChanged(); } }); @@ -91,11 +94,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @Override - public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) { - enforceSuggestPhoneTimePermission(); + public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) { + enforceSuggestTelephonyTimePermission(); Objects.requireNonNull(timeSignal); - mHandler.post(() -> mTimeDetectorStrategy.suggestPhoneTime(timeSignal)); + mHandler.post(() -> mTimeDetectorStrategy.suggestTelephonyTime(timeSignal)); } @Override @@ -114,8 +117,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal)); } + /** Internal method for handling the auto time setting being changed. */ @VisibleForTesting - public void handleAutoTimeDetectionToggle() { + public void handleAutoTimeDetectionChanged() { mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); } @@ -127,10 +131,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mTimeDetectorStrategy.dump(pw, args); } - private void enforceSuggestPhoneTimePermission() { + private void enforceSuggestTelephonyTimePermission() { mContext.enforceCallingPermission( - android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE, - "suggest phone time and time zone"); + android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE, + "suggest telephony time and time zone"); } private void enforceSuggestManualTimePermission() { diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 468b806d6dce..a5fba4e6ba49 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -20,14 +20,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import java.io.PrintWriter; /** - * The interface for classes that implement the time detection algorithm used by the - * TimeDetectorService. + * The interface for the class that implements the time detection algorithm used by the + * {@link TimeDetectorService}. * * <p>Most calls will be handled by a single thread but that is not true for all calls. For example * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must @@ -78,7 +78,7 @@ public interface TimeDetectorStrategy { void initialize(@NonNull Callback callback); /** Process the suggested time from telephony sources. */ - void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion); + void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion); /** Process the suggested manually entered time. */ void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index a1e643f15a8e..8c54fa95e04b 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import android.util.LocalLog; import android.util.Slog; @@ -38,9 +38,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to - * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used - * unless the data becomes too stale. + * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to + * {@link AlarmManager}. When there are multiple telephony sources, the one with the lowest ID is + * used unless the data becomes too stale. * * <p>Most public methods are marked synchronized to ensure thread safety around internal state. */ @@ -50,23 +50,26 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; /** A score value used to indicate "no score", either due to validation failure or age. */ - private static final int PHONE_INVALID_SCORE = -1; - /** The number of buckets phone suggestions can be put in by age. */ - private static final int PHONE_BUCKET_COUNT = 24; + private static final int TELEPHONY_INVALID_SCORE = -1; + /** The number of buckets telephony suggestions can be put in by age. */ + private static final int TELEPHONY_BUCKET_COUNT = 24; /** Each bucket is this size. All buckets are equally sized. */ @VisibleForTesting - static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; - /** Phone and network suggestions older than this value are considered too old to be used. */ + static final int TELEPHONY_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; + /** + * Telephony and network suggestions older than this value are considered too old to be used. + */ @VisibleForTesting - static final long MAX_UTC_TIME_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS; + static final long MAX_UTC_TIME_AGE_MILLIS = + TELEPHONY_BUCKET_COUNT * TELEPHONY_BUCKET_SIZE_MILLIS; - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL, ORIGIN_NETWORK }) + @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK }) @Retention(RetentionPolicy.SOURCE) public @interface Origin {} /** Used when a time value originated from a telephony signal. */ @Origin - private static final int ORIGIN_PHONE = 1; + private static final int ORIGIN_TELEPHONY = 1; /** Used when a time value originated from a user / manual settings. */ @Origin @@ -83,7 +86,9 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; - /** The number of previous phone suggestions to keep for each ID (for use during debugging). */ + /** + * The number of previous telephony suggestions to keep for each ID (for use during debugging). + */ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30; // A log for changes made to the system clock and why. @@ -106,7 +111,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { * stable. */ @GuardedBy("this") - private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex = + private final ArrayMapWithHistory<Integer, TelephonyTimeSuggestion> mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); @GuardedBy("this") @@ -144,7 +149,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override - public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) { // Empty time suggestion means that telephony network connectivity has been lost. // The passage of time is relentless, and we don't expect our users to use a time machine, // so we can continue relying on previous suggestions when we lose connectivity. This is @@ -157,13 +162,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // Perform validation / input filtering and record the validated suggestion against the // slotIndex. - if (!validateAndStorePhoneSuggestion(timeSuggestion)) { + if (!validateAndStoreTelephonySuggestion(timeSuggestion)) { return; } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. - String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion; + String reason = "New telephony time suggested. timeSuggestion=" + timeSuggestion; doAutoTimeDetection(reason); } @@ -201,7 +206,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { mTimeChangesLog.dump(ipw); ipw.decreaseIndent(); // level 2 - ipw.println("Phone suggestion history:"); + ipw.println("Telephony suggestion history:"); ipw.increaseIndent(); // level 2 mSuggestionBySlotIndex.dump(ipw); ipw.decreaseIndent(); // level 2 @@ -216,7 +221,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @GuardedBy("this") - private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) { + private boolean validateAndStoreTelephonySuggestion( + @NonNull TelephonyTimeSuggestion suggestion) { TimestampedValue<Long> newUtcTime = suggestion.getUtcTime(); if (!validateSuggestionTime(newUtcTime, suggestion)) { // There's probably nothing useful we can do: elsewhere we assume that reference @@ -225,7 +231,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } int slotIndex = suggestion.getSlotIndex(); - PhoneTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex); + TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex); if (previousSuggestion != null) { // We can log / discard suggestions with obvious issues with the reference time clock. if (previousSuggestion.getUtcTime() == null @@ -241,7 +247,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { newUtcTime, previousSuggestion.getUtcTime()); if (referenceTimeDifference < 0) { // The reference time is before the previously received suggestion. Ignore it. - Slog.w(LOG_TAG, "Out of order phone suggestion received." + Slog.w(LOG_TAG, "Out of order telephony suggestion received." + " referenceTimeDifference=" + referenceTimeDifference + " previousSuggestion=" + previousSuggestion + " suggestion=" + suggestion); @@ -282,18 +288,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // Android devices currently prioritize any telephony over network signals. There are // carrier compliance tests that would need to be changed before we could ignore NITZ or - // prefer NTP generally. This check is cheap on devices without phone hardware. - PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); - if (bestPhoneSuggestion != null) { - final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); - String cause = "Found good phone suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion + // prefer NTP generally. This check is cheap on devices without telephony hardware. + TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion(); + if (bestTelephonySuggestion != null) { + final TimestampedValue<Long> newUtcTime = bestTelephonySuggestion.getUtcTime(); + String cause = "Found good telephony suggestion." + + ", bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason; - setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); + setSystemClockIfRequired(ORIGIN_TELEPHONY, newUtcTime, cause); return; } - // There is no good phone suggestion, try network. + // There is no good telephony suggestion, try network. NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion(); if (networkSuggestion != null) { final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime(); @@ -305,18 +311,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } if (DBG) { - Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion." + Slog.d(LOG_TAG, "Could not determine time: No best telephony or network suggestion." + " detectionReason=" + detectionReason); } } @GuardedBy("this") @Nullable - private PhoneTimeSuggestion findBestPhoneSuggestion() { + private TelephonyTimeSuggestion findBestTelephonySuggestion() { long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); - // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These - // have a number of limitations: + // Telephony time suggestions are assumed to be derived from NITZ or NITZ-like signals. + // These have a number of limitations: // 1) No guarantee of accuracy ("accuracy of the time information is in the order of // minutes") [1] // 2) No guarantee of regular signals ("dependent on the handset crossing radio network @@ -335,8 +341,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // For simplicity, we try to value recency, then consistency of slotIndex. // // The heuristic works as follows: - // Recency: The most recent suggestion from each phone is scored. The score is based on a - // discrete age bucket, i.e. so signals received around the same time will be in the same + // Recency: The most recent suggestion from each slotIndex is scored. The score is based on + // a discrete age bucket, i.e. so signals received around the same time will be in the same // bucket, thus applying a loose reference time ordering. The suggestion with the highest // score is used. // Consistency: If there a multiple suggestions with the same score, the suggestion with the @@ -345,11 +351,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // In the trivial case with a single ID this will just mean that the latest received // suggestion is used. - PhoneTimeSuggestion bestSuggestion = null; - int bestScore = PHONE_INVALID_SCORE; + TelephonyTimeSuggestion bestSuggestion = null; + int bestScore = TELEPHONY_INVALID_SCORE; for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { Integer slotIndex = mSuggestionBySlotIndex.keyAt(i); - PhoneTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); + TelephonyTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); if (candidateSuggestion == null) { // Unexpected - null suggestions should never be stored. Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for slotIndex." @@ -362,8 +368,9 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { continue; } - int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion); - if (candidateScore == PHONE_INVALID_SCORE) { + int candidateScore = + scoreTelephonySuggestion(elapsedRealtimeMillis, candidateSuggestion); + if (candidateScore == TELEPHONY_INVALID_SCORE) { // Expected: This means the suggestion is obviously invalid or just too old. continue; } @@ -384,8 +391,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return bestSuggestion; } - private static int scorePhoneSuggestion( - long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) { + private static int scoreTelephonySuggestion( + long elapsedRealtimeMillis, @NonNull TelephonyTimeSuggestion timeSuggestion) { // Validate first. TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime(); @@ -393,21 +400,21 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { Slog.w(LOG_TAG, "Existing suggestion found to be invalid " + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + ", timeSuggestion=" + timeSuggestion); - return PHONE_INVALID_SCORE; + return TELEPHONY_INVALID_SCORE; } // The score is based on the age since receipt. Suggestions are bucketed so two // suggestions in the same bucket from different slotIndexs are scored the same. long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis(); - // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT. - int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS); - if (bucketIndex >= PHONE_BUCKET_COUNT) { - return PHONE_INVALID_SCORE; + // Turn the age into a discrete value: 0 <= bucketIndex < TELEPHONY_BUCKET_COUNT. + int bucketIndex = (int) (ageMillis / TELEPHONY_BUCKET_SIZE_MILLIS); + if (bucketIndex >= TELEPHONY_BUCKET_COUNT) { + return TELEPHONY_INVALID_SCORE; } // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT. - return PHONE_BUCKET_COUNT - bucketIndex; + return TELEPHONY_BUCKET_COUNT - bucketIndex; } /** Returns the latest, valid, network suggestion. Returns {@code null} if there isn't one. */ @@ -537,13 +544,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } /** - * Returns the current best phone suggestion. Not intended for general use: it is used during - * tests to check strategy behavior. + * Returns the current best telephony suggestion. Not intended for general use: it is used + * during tests to check strategy behavior. */ @VisibleForTesting @Nullable - public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() { - return findBestPhoneSuggestion(); + public synchronized TelephonyTimeSuggestion findBestTelephonySuggestionForTests() { + return findBestTelephonySuggestion(); } /** @@ -561,7 +568,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @VisibleForTesting @Nullable - public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int slotIndex) { + public synchronized TelephonyTimeSuggestion getLatestTelephonySuggestion(int slotIndex) { return mSuggestionBySlotIndex.get(slotIndex); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java index adf6d7e51f4f..2520316b5d54 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java @@ -24,9 +24,9 @@ import android.os.SystemProperties; import android.provider.Settings; /** - * The real implementation of {@link TimeZoneDetectorStrategy.Callback}. + * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}. */ -public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback { +public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback { private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 9a1fe6501221..57b6ec9062a8 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -67,19 +67,21 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub private static TimeZoneDetectorService create(@NonNull Context context) { final TimeZoneDetectorStrategy timeZoneDetectorStrategy = - TimeZoneDetectorStrategy.create(context); + TimeZoneDetectorStrategyImpl.create(context); Handler handler = FgThread.getHandler(); + TimeZoneDetectorService service = + new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy); + ContentResolver contentResolver = context.getContentResolver(); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, new ContentObserver(handler) { public void onChange(boolean selfChange) { - timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange(); + service.handleAutoTimeZoneDetectionChanged(); } }); - - return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy); + return service; } @VisibleForTesting @@ -99,11 +101,11 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } @Override - public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { - enforceSuggestPhoneTimeZonePermission(); + public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) { + enforceSuggestTelephonyTimeZonePermission(); Objects.requireNonNull(timeZoneSuggestion); - mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion)); + mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion)); } @Override @@ -111,17 +113,25 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - mTimeZoneDetectorStrategy.dumpState(pw, args); + mTimeZoneDetectorStrategy.dump(pw, args); + } + + /** Internal method for handling the auto time zone setting being changed. */ + @VisibleForTesting + public void handleAutoTimeZoneDetectionChanged() { + mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged); } - private void enforceSuggestPhoneTimeZonePermission() { + private void enforceSuggestTelephonyTimeZonePermission() { mContext.enforceCallingPermission( - android.Manifest.permission.SET_TIME_ZONE, "set time zone"); + android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE, + "suggest telephony time and time zone"); } private void enforceSuggestManualTimeZonePermission() { mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.SET_TIME_ZONE, "set time zone"); + android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE, + "suggest manual time and time zone"); } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index b0e006908231..0eb27cc5cff0 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -15,507 +15,44 @@ */ package com.android.server.timezonedetector; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE; - -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; -import android.content.Context; -import android.util.LocalLog; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; /** - * A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and - * suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent - * on the current "auto time zone detection" setting. + * The interface for the class that implement the time detection algorithm used by the + * {@link TimeZoneDetectorService}. + * + * <p>Most calls will be handled by a single thread but that is not true for all calls. For example + * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must + * handle thread safety. * - * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses - * the best suggestion based on a scoring algorithm. If several phones provide the same score then - * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer - * possible to be confident about the time zone, phones must submit an empty suggestion in order to - * "withdraw" their previous suggestion. + * @hide */ -public class TimeZoneDetectorStrategy { - - /** - * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be - * faked for tests. - * - * <p>Note: Because the system properties-derived values like - * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()}, - * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and - * processes!), their use are prone to race conditions. That will be true until the - * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}. - */ - @VisibleForTesting - public interface Callback { - - /** - * Returns true if automatic time zone detection is enabled in settings. - */ - boolean isAutoTimeZoneDetectionEnabled(); - - /** - * Returns true if the device has had an explicit time zone set. - */ - boolean isDeviceTimeZoneInitialized(); - - /** - * Returns the device's currently configured time zone. - */ - String getDeviceTimeZone(); - - /** - * Sets the device's time zone. - */ - void setDeviceTimeZone(@NonNull String zoneId); - } - - private static final String LOG_TAG = "TimeZoneDetectorStrategy"; - private static final boolean DBG = false; +public interface TimeZoneDetectorStrategy { - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) - @Retention(RetentionPolicy.SOURCE) - public @interface Origin {} - - /** Used when a time value originated from a telephony signal. */ - @Origin - private static final int ORIGIN_PHONE = 1; - - /** Used when a time value originated from a user / manual settings. */ - @Origin - private static final int ORIGIN_MANUAL = 2; - - /** - * The abstract score for an empty or invalid phone suggestion. - * - * Used to score phone suggestions where there is no zone. - */ - @VisibleForTesting - public static final int PHONE_SCORE_NONE = 0; - - /** - * The abstract score for a low quality phone suggestion. - * - * Used to score suggestions where: - * The suggested zone ID is one of several possibilities, and the possibilities have different - * offsets. - * - * You would have to be quite desperate to want to use this choice. - */ - @VisibleForTesting - public static final int PHONE_SCORE_LOW = 1; - - /** - * The abstract score for a medium quality phone suggestion. - * - * Used for: - * The suggested zone ID is one of several possibilities but at least the possibilities have the - * same offset. Users would get the correct time but for the wrong reason. i.e. their device may - * switch to DST at the wrong time and (for example) their calendar events. - */ - @VisibleForTesting - public static final int PHONE_SCORE_MEDIUM = 2; - - /** - * The abstract score for a high quality phone suggestion. - * - * Used for: - * The suggestion was for one zone ID and the answer was unambiguous and likely correct given - * the info available. - */ - @VisibleForTesting - public static final int PHONE_SCORE_HIGH = 3; - - /** - * The abstract score for a highest quality phone suggestion. - * - * Used for: - * Suggestions that must "win" because they constitute test or emulator zone ID. - */ - @VisibleForTesting - public static final int PHONE_SCORE_HIGHEST = 4; - - /** - * The threshold at which phone suggestions are good enough to use to set the device's time - * zone. - */ - @VisibleForTesting - public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM; - - /** The number of previous phone suggestions to keep for each ID (for use during debugging). */ - private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30; - - @NonNull - private final Callback mCallback; - - /** - * A log that records the decisions / decision metadata that affected the device's time zone - * (for use during debugging). - */ - @NonNull - private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); - - /** - * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two - * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to - * be stable. - */ - @GuardedBy("this") - private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex = - new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE); - - /** - * Creates a new instance of {@link TimeZoneDetectorStrategy}. - */ - public static TimeZoneDetectorStrategy create(Context context) { - Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context); - return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper); - } - - @VisibleForTesting - public TimeZoneDetectorStrategy(Callback callback) { - mCallback = Objects.requireNonNull(callback); - } - - /** Process the suggested manually- / user-entered time zone. */ - public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) { - Objects.requireNonNull(suggestion); - - String timeZoneId = suggestion.getZoneId(); - String cause = "Manual time suggestion received: suggestion=" + suggestion; - setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause); - } + /** Process the suggested manually-entered (i.e. user sourced) time zone. */ + void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion); /** * Suggests a time zone for the device, or withdraws a previous suggestion if - * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a - * specific {@link PhoneTimeZoneSuggestion#getSlotIndex() phone}. - * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a + * {@link TelephonyTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to + * a specific {@link TelephonyTimeZoneSuggestion#getSlotIndex() slotIndex}. + * See {@link TelephonyTimeZoneSuggestion} for an explanation of the metadata associated with a * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone * setting and what to set it to. */ - public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) { - if (DBG) { - Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion); - } - Objects.requireNonNull(suggestion); - - // Score the suggestion. - int score = scorePhoneSuggestion(suggestion); - QualifiedPhoneTimeZoneSuggestion scoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(suggestion, score); - - // Store the suggestion against the correct slotIndex. - mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); - - // Now perform auto time zone detection. The new suggestion may be used to modify the time - // zone setting. - String reason = "New phone time suggested. suggestion=" + suggestion; - doAutoTimeZoneDetection(reason); - } - - private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) { - int score; - if (suggestion.getZoneId() == null) { - score = PHONE_SCORE_NONE; - } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY - || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) { - // Handle emulator / test cases : These suggestions should always just be used. - score = PHONE_SCORE_HIGHEST; - } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) { - score = PHONE_SCORE_HIGH; - } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) { - // The suggestion may be wrong, but at least the offset should be correct. - score = PHONE_SCORE_MEDIUM; - } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) { - // The suggestion has a good chance of being wrong. - score = PHONE_SCORE_LOW; - } else { - throw new AssertionError(); - } - return score; - } - - /** - * Finds the best available time zone suggestion from all phones. If it is high-enough quality - * and automatic time zone detection is enabled then it will be set on the device. The outcome - * can be that this strategy becomes / remains un-opinionated and nothing is set. - */ - @GuardedBy("this") - private void doAutoTimeZoneDetection(@NonNull String detectionReason) { - if (!mCallback.isAutoTimeZoneDetectionEnabled()) { - // Avoid doing unnecessary work with this (race-prone) check. - return; - } - - QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); - - // Work out what to do with the best suggestion. - if (bestPhoneSuggestion == null) { - // There is no phone suggestion available at all. Become un-opinionated. - if (DBG) { - Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion." - + " detectionReason=" + detectionReason); - } - return; - } - - // Special case handling for uninitialized devices. This should only happen once. - String newZoneId = bestPhoneSuggestion.suggestion.getZoneId(); - if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) { - String cause = "Device has no time zone set. Attempting to set the device to the best" - + " available suggestion." - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason; - Slog.i(LOG_TAG, cause); - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause); - return; - } - - boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD; - if (!suggestionGoodEnough) { - if (DBG) { - Slog.d(LOG_TAG, "Best suggestion not good enough." - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason); - } - return; - } - - // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time - // zone ID. - if (newZoneId == null) { - Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + " detectionReason=" + detectionReason); - return; - } - - String zoneId = bestPhoneSuggestion.suggestion.getZoneId(); - String cause = "Found good suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason; - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause); - } - - @GuardedBy("this") - private void setDeviceTimeZoneIfRequired( - @Origin int origin, @NonNull String newZoneId, @NonNull String cause) { - Objects.requireNonNull(newZoneId); - Objects.requireNonNull(cause); - - boolean isOriginAutomatic = isOriginAutomatic(origin); - if (isOriginAutomatic) { - if (!mCallback.isAutoTimeZoneDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time zone detection is not enabled." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - } else { - if (mCallback.isAutoTimeZoneDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time zone detection is enabled." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - } - - String currentZoneId = mCallback.getDeviceTimeZone(); - - // Avoid unnecessary changes / intents. - if (newZoneId.equals(currentZoneId)) { - // No need to set the device time zone - the setting is already what we would be - // suggesting. - if (DBG) { - Slog.d(LOG_TAG, "No need to change the time zone;" - + " device is already set to the suggested zone." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - - mCallback.setDeviceTimeZone(newZoneId); - String msg = "Set device time zone." - + " origin=" + origin - + ", currentZoneId=" + currentZoneId - + ", newZoneId=" + newZoneId - + ", cause=" + cause; - if (DBG) { - Slog.d(LOG_TAG, msg); - } - mTimeZoneChangesLog.log(msg); - } - - private static boolean isOriginAutomatic(@Origin int origin) { - return origin != ORIGIN_MANUAL; - } - - @GuardedBy("this") - @Nullable - private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() { - QualifiedPhoneTimeZoneSuggestion bestSuggestion = null; - - // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone - // and find the best. Note that we deliberately do not look at age: the caller can - // rate-limit so age is not a strong indicator of confidence. Instead, the callers are - // expected to withdraw suggestions they no longer have confidence in. - for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { - QualifiedPhoneTimeZoneSuggestion candidateSuggestion = - mSuggestionBySlotIndex.valueAt(i); - if (candidateSuggestion == null) { - // Unexpected - continue; - } - - if (bestSuggestion == null) { - bestSuggestion = candidateSuggestion; - } else if (candidateSuggestion.score > bestSuggestion.score) { - bestSuggestion = candidateSuggestion; - } else if (candidateSuggestion.score == bestSuggestion.score) { - // Tie! Use the suggestion with the lowest slotIndex. - int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex(); - int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex(); - if (candidateSlotIndex < bestSlotIndex) { - bestSuggestion = candidateSuggestion; - } - } - } - return bestSuggestion; - } - - /** - * Returns the current best phone suggestion. Not intended for general use: it is used during - * tests to check strategy behavior. - */ - @VisibleForTesting - @Nullable - public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() { - return findBestPhoneSuggestion(); - } + void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); /** * Called when there has been a change to the automatic time zone detection setting. */ - @VisibleForTesting - public synchronized void handleAutoTimeZoneDetectionChange() { - if (DBG) { - Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called"); - } - if (mCallback.isAutoTimeZoneDetectionEnabled()) { - // When the user enabled time zone detection, run the time zone detection and change the - // device time zone if possible. - String reason = "Auto time zone detection setting enabled."; - doAutoTimeZoneDetection(reason); - } - } + void handleAutoTimeZoneDetectionChanged(); /** * Dumps internal state such as field values. */ - public synchronized void dumpState(PrintWriter pw, String[] args) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println("TimeZoneDetectorStrategy:"); - - ipw.increaseIndent(); // level 1 - ipw.println("mCallback.isTimeZoneDetectionEnabled()=" - + mCallback.isAutoTimeZoneDetectionEnabled()); - ipw.println("mCallback.isDeviceTimeZoneInitialized()=" - + mCallback.isDeviceTimeZoneInitialized()); - ipw.println("mCallback.getDeviceTimeZone()=" - + mCallback.getDeviceTimeZone()); - - ipw.println("Time zone change log:"); - ipw.increaseIndent(); // level 2 - mTimeZoneChangesLog.dump(ipw); - ipw.decreaseIndent(); // level 2 - - ipw.println("Phone suggestion history:"); - ipw.increaseIndent(); // level 2 - mSuggestionBySlotIndex.dump(ipw); - ipw.decreaseIndent(); // level 2 - ipw.decreaseIndent(); // level 1 - ipw.flush(); - } - - /** - * A method used to inspect strategy state during tests. Not intended for general use. - */ - @VisibleForTesting - public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) { - return mSuggestionBySlotIndex.get(slotIndex); - } - - /** - * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata. - */ - @VisibleForTesting - public static class QualifiedPhoneTimeZoneSuggestion { - - @VisibleForTesting - public final PhoneTimeZoneSuggestion suggestion; - - /** - * The score the suggestion has been given. This can be used to rank against other - * suggestions of the same type. - */ - @VisibleForTesting - public final int score; - - @VisibleForTesting - public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) { - this.suggestion = suggestion; - this.score = score; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o; - return score == that.score - && suggestion.equals(that.suggestion); - } - - @Override - public int hashCode() { - return Objects.hash(score, suggestion); - } - - @Override - public String toString() { - return "QualifiedPhoneTimeZoneSuggestion{" - + "suggestion=" + suggestion - + ", score=" + score - + '}'; - } - } + void dump(PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java new file mode 100644 index 000000000000..652dbe153680 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -0,0 +1,522 @@ +/* + * Copyright 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 com.android.server.timezonedetector; + +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.content.Context; +import android.util.LocalLog; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual + * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time + * zone detection" setting. + * + * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each + * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes + * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation + * changes and it is no longer possible to be confident about the time zone, slotIndexes must have + * an empty suggestion submitted in order to "withdraw" their previous suggestion. + * + * <p>Most public methods are marked synchronized to ensure thread safety around internal state. + */ +public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy { + + /** + * Used by {@link TimeZoneDetectorStrategyImpl} to interact with the surrounding service. It can + * be faked for tests. + * + * <p>Note: Because the system properties-derived values like + * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()}, + * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and + * processes!), their use are prone to race conditions. That will be true until the + * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategyImpl}. + */ + @VisibleForTesting + public interface Callback { + + /** + * Returns true if automatic time zone detection is enabled in settings. + */ + boolean isAutoTimeZoneDetectionEnabled(); + + /** + * Returns true if the device has had an explicit time zone set. + */ + boolean isDeviceTimeZoneInitialized(); + + /** + * Returns the device's currently configured time zone. + */ + String getDeviceTimeZone(); + + /** + * Sets the device's time zone. + */ + void setDeviceTimeZone(@NonNull String zoneId); + } + + private static final String LOG_TAG = "TimeZoneDetectorStrategy"; + private static final boolean DBG = false; + + @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL }) + @Retention(RetentionPolicy.SOURCE) + public @interface Origin {} + + /** Used when a time value originated from a telephony signal. */ + @Origin + private static final int ORIGIN_TELEPHONY = 1; + + /** Used when a time value originated from a user / manual settings. */ + @Origin + private static final int ORIGIN_MANUAL = 2; + + /** + * The abstract score for an empty or invalid telephony suggestion. + * + * Used to score telephony suggestions where there is no zone. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_NONE = 0; + + /** + * The abstract score for a low quality telephony suggestion. + * + * Used to score suggestions where: + * The suggested zone ID is one of several possibilities, and the possibilities have different + * offsets. + * + * You would have to be quite desperate to want to use this choice. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_LOW = 1; + + /** + * The abstract score for a medium quality telephony suggestion. + * + * Used for: + * The suggested zone ID is one of several possibilities but at least the possibilities have the + * same offset. Users would get the correct time but for the wrong reason. i.e. their device may + * switch to DST at the wrong time and (for example) their calendar events. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_MEDIUM = 2; + + /** + * The abstract score for a high quality telephony suggestion. + * + * Used for: + * The suggestion was for one zone ID and the answer was unambiguous and likely correct given + * the info available. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_HIGH = 3; + + /** + * The abstract score for a highest quality telephony suggestion. + * + * Used for: + * Suggestions that must "win" because they constitute test or emulator zone ID. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_HIGHEST = 4; + + /** + * The threshold at which telephony suggestions are good enough to use to set the device's time + * zone. + */ + @VisibleForTesting + 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). + */ + private static final int KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE = 30; + + @NonNull + private final Callback mCallback; + + /** + * A log that records the decisions / decision metadata that affected the device's time zone + * (for use during debugging). + */ + @NonNull + private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); + + /** + * A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two + * mappings: devices will have a small number of telephony devices and slotIndexes are assumed + * to be stable. + */ + @GuardedBy("this") + private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion> + mSuggestionBySlotIndex = + new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE); + + /** + * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. + */ + public static TimeZoneDetectorStrategyImpl create(Context context) { + Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context); + return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper); + } + + @VisibleForTesting + public TimeZoneDetectorStrategyImpl(Callback callback) { + mCallback = Objects.requireNonNull(callback); + } + + @Override + public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) { + Objects.requireNonNull(suggestion); + + String timeZoneId = suggestion.getZoneId(); + String cause = "Manual time suggestion received: suggestion=" + suggestion; + setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause); + } + + @Override + public synchronized void suggestTelephonyTimeZone( + @NonNull TelephonyTimeZoneSuggestion suggestion) { + if (DBG) { + Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion); + } + Objects.requireNonNull(suggestion); + + // Score the suggestion. + int score = scoreTelephonySuggestion(suggestion); + QualifiedTelephonyTimeZoneSuggestion scoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(suggestion, score); + + // Store the suggestion against the correct slotIndex. + mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); + + // Now perform auto time zone detection. The new suggestion may be used to modify the time + // zone setting. + String reason = "New telephony time suggested. suggestion=" + suggestion; + doAutoTimeZoneDetection(reason); + } + + private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) { + int score; + if (suggestion.getZoneId() == null) { + score = TELEPHONY_SCORE_NONE; + } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY + || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) { + // Handle emulator / test cases : These suggestions should always just be used. + score = TELEPHONY_SCORE_HIGHEST; + } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) { + score = TELEPHONY_SCORE_HIGH; + } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) { + // The suggestion may be wrong, but at least the offset should be correct. + score = TELEPHONY_SCORE_MEDIUM; + } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) { + // The suggestion has a good chance of being wrong. + score = TELEPHONY_SCORE_LOW; + } else { + throw new AssertionError(); + } + return score; + } + + /** + * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough + * quality and automatic time zone detection is enabled then it will be set on the device. The + * outcome can be that this strategy becomes / remains un-opinionated and nothing is set. + */ + @GuardedBy("this") + private void doAutoTimeZoneDetection(@NonNull String detectionReason) { + if (!mCallback.isAutoTimeZoneDetectionEnabled()) { + // Avoid doing unnecessary work with this (race-prone) check. + return; + } + + QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion = + findBestTelephonySuggestion(); + + // Work out what to do with the best suggestion. + if (bestTelephonySuggestion == null) { + // There is no telephony suggestion available at all. Become un-opinionated. + if (DBG) { + Slog.d(LOG_TAG, "Could not determine time zone: No best telephony suggestion." + + " detectionReason=" + detectionReason); + } + return; + } + + // Special case handling for uninitialized devices. This should only happen once. + String newZoneId = bestTelephonySuggestion.suggestion.getZoneId(); + if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) { + String cause = "Device has no time zone set. Attempting to set the device to the best" + + " available suggestion." + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason; + Slog.i(LOG_TAG, cause); + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, newZoneId, cause); + return; + } + + boolean suggestionGoodEnough = + bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD; + if (!suggestionGoodEnough) { + if (DBG) { + Slog.d(LOG_TAG, "Best suggestion not good enough." + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason); + } + return; + } + + // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time + // zone ID. + if (newZoneId == null) { + Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + " detectionReason=" + detectionReason); + return; + } + + String zoneId = bestTelephonySuggestion.suggestion.getZoneId(); + String cause = "Found good suggestion." + + ", bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason; + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, zoneId, cause); + } + + @GuardedBy("this") + private void setDeviceTimeZoneIfRequired( + @Origin int origin, @NonNull String newZoneId, @NonNull String cause) { + Objects.requireNonNull(newZoneId); + Objects.requireNonNull(cause); + + boolean isOriginAutomatic = isOriginAutomatic(origin); + if (isOriginAutomatic) { + if (!mCallback.isAutoTimeZoneDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time zone detection is not enabled." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + } else { + if (mCallback.isAutoTimeZoneDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time zone detection is enabled." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + } + + String currentZoneId = mCallback.getDeviceTimeZone(); + + // Avoid unnecessary changes / intents. + if (newZoneId.equals(currentZoneId)) { + // No need to set the device time zone - the setting is already what we would be + // suggesting. + if (DBG) { + Slog.d(LOG_TAG, "No need to change the time zone;" + + " device is already set to the suggested zone." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + + mCallback.setDeviceTimeZone(newZoneId); + String msg = "Set device time zone." + + " origin=" + origin + + ", currentZoneId=" + currentZoneId + + ", newZoneId=" + newZoneId + + ", cause=" + cause; + if (DBG) { + Slog.d(LOG_TAG, msg); + } + mTimeZoneChangesLog.log(msg); + } + + private static boolean isOriginAutomatic(@Origin int origin) { + return origin != ORIGIN_MANUAL; + } + + @GuardedBy("this") + @Nullable + private QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestion() { + QualifiedTelephonyTimeZoneSuggestion bestSuggestion = null; + + // Iterate over the latest QualifiedTelephonyTimeZoneSuggestion objects received for each + // slotIndex and find the best. Note that we deliberately do not look at age: the caller can + // rate-limit so age is not a strong indicator of confidence. Instead, the callers are + // expected to withdraw suggestions they no longer have confidence in. + for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { + QualifiedTelephonyTimeZoneSuggestion candidateSuggestion = + mSuggestionBySlotIndex.valueAt(i); + if (candidateSuggestion == null) { + // Unexpected + continue; + } + + if (bestSuggestion == null) { + bestSuggestion = candidateSuggestion; + } else if (candidateSuggestion.score > bestSuggestion.score) { + bestSuggestion = candidateSuggestion; + } else if (candidateSuggestion.score == bestSuggestion.score) { + // Tie! Use the suggestion with the lowest slotIndex. + int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex(); + int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex(); + if (candidateSlotIndex < bestSlotIndex) { + bestSuggestion = candidateSuggestion; + } + } + } + return bestSuggestion; + } + + /** + * Returns the current best telephony suggestion. Not intended for general use: it is used + * during tests to check strategy behavior. + */ + @VisibleForTesting + @Nullable + public synchronized QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestionForTests() { + return findBestTelephonySuggestion(); + } + + @Override + public synchronized void handleAutoTimeZoneDetectionChanged() { + if (DBG) { + Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called"); + } + if (mCallback.isAutoTimeZoneDetectionEnabled()) { + // When the user enabled time zone detection, run the time zone detection and change the + // device time zone if possible. + String reason = "Auto time zone detection setting enabled."; + doAutoTimeZoneDetection(reason); + } + } + + /** + * Dumps internal state such as field values. + */ + @Override + public synchronized void dump(PrintWriter pw, String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("TimeZoneDetectorStrategy:"); + + ipw.increaseIndent(); // level 1 + ipw.println("mCallback.isTimeZoneDetectionEnabled()=" + + mCallback.isAutoTimeZoneDetectionEnabled()); + ipw.println("mCallback.isDeviceTimeZoneInitialized()=" + + mCallback.isDeviceTimeZoneInitialized()); + ipw.println("mCallback.getDeviceTimeZone()=" + + mCallback.getDeviceTimeZone()); + + ipw.println("Time zone change log:"); + ipw.increaseIndent(); // level 2 + mTimeZoneChangesLog.dump(ipw); + ipw.decreaseIndent(); // level 2 + + ipw.println("Telephony suggestion history:"); + ipw.increaseIndent(); // level 2 + mSuggestionBySlotIndex.dump(ipw); + ipw.decreaseIndent(); // level 2 + ipw.decreaseIndent(); // level 1 + ipw.flush(); + } + + /** + * A method used to inspect strategy state during tests. Not intended for general use. + */ + @VisibleForTesting + public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion( + int slotIndex) { + return mSuggestionBySlotIndex.get(slotIndex); + } + + /** + * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. + */ + @VisibleForTesting + public static class QualifiedTelephonyTimeZoneSuggestion { + + @VisibleForTesting + public final TelephonyTimeZoneSuggestion suggestion; + + /** + * The score the suggestion has been given. This can be used to rank against other + * suggestions of the same type. + */ + @VisibleForTesting + public final int score; + + @VisibleForTesting + public QualifiedTelephonyTimeZoneSuggestion( + TelephonyTimeZoneSuggestion suggestion, int score) { + this.suggestion = suggestion; + this.score = score; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifiedTelephonyTimeZoneSuggestion that = (QualifiedTelephonyTimeZoneSuggestion) o; + return score == that.score + && suggestion.equals(that.suggestion); + } + + @Override + public int hashCode() { + return Objects.hash(score, suggestion); + } + + @Override + public String toString() { + return "QualifiedTelephonyTimeZoneSuggestion{" + + "suggestion=" + suggestion + + ", score=" + score + + '}'; + } + } +} diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 4f010d5e0f2c..e3b1152cd7b7 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -383,36 +383,38 @@ public final class TvInputManagerService extends SystemService { if (mCurrentUserId == userId) { return; } - UserState userState = mUserStates.get(mCurrentUserId); - List<SessionState> sessionStatesToRelease = new ArrayList<>(); - for (SessionState sessionState : userState.sessionStateMap.values()) { - if (sessionState.session != null && !sessionState.isRecordingSession) { - sessionStatesToRelease.add(sessionState); + if (mUserStates.contains(mCurrentUserId)) { + UserState userState = mUserStates.get(mCurrentUserId); + List<SessionState> sessionStatesToRelease = new ArrayList<>(); + for (SessionState sessionState : userState.sessionStateMap.values()) { + if (sessionState.session != null && !sessionState.isRecordingSession) { + sessionStatesToRelease.add(sessionState); + } } - } - for (SessionState sessionState : sessionStatesToRelease) { - try { - sessionState.session.release(); - } catch (RemoteException e) { - Slog.e(TAG, "error in release", e); + for (SessionState sessionState : sessionStatesToRelease) { + try { + sessionState.session.release(); + } catch (RemoteException e) { + Slog.e(TAG, "error in release", e); + } + clearSessionAndNotifyClientLocked(sessionState); } - clearSessionAndNotifyClientLocked(sessionState); - } - for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator(); - it.hasNext(); ) { - ComponentName component = it.next(); - ServiceState serviceState = userState.serviceStateMap.get(component); - if (serviceState != null && serviceState.sessionTokens.isEmpty()) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); + for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator(); + it.hasNext(); ) { + ComponentName component = it.next(); + ServiceState serviceState = userState.serviceStateMap.get(component); + if (serviceState != null && serviceState.sessionTokens.isEmpty()) { + if (serviceState.callback != null) { + try { + serviceState.service.unregisterCallback(serviceState.callback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } } + mContext.unbindService(serviceState.connection); + it.remove(); } - mContext.unbindService(serviceState.connection); - it.remove(); } } @@ -490,6 +492,10 @@ public final class TvInputManagerService extends SystemService { userState.mainSessionToken = null; mUserStates.remove(userId); + + if (userId == mCurrentUserId) { + switchUser(UserHandle.USER_SYSTEM); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ed38e9a73050..a54f5d43751c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1629,12 +1629,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A requestedVrComponent = (aInfo.requestedVrComponent == null) ? null : ComponentName.unflattenFromString(aInfo.requestedVrComponent); - lockTaskLaunchMode = aInfo.lockTaskLaunchMode; - if (info.applicationInfo.isPrivilegedApp() - && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS - || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { - lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; - } + lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options); if (options != null) { pendingOptions = options; @@ -1642,13 +1637,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (usageReport != null) { appTimeTracker = new AppTimeTracker(usageReport); } - final boolean useLockTask = pendingOptions.getLockTaskMode(); + // Gets launch display id from options. It returns INVALID_DISPLAY if not set. + mHandoverLaunchDisplayId = options.getLaunchDisplayId(); + } + } + + static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) { + int lockTaskLaunchMode = aInfo.lockTaskLaunchMode; + if (aInfo.applicationInfo.isPrivilegedApp() + && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS + || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { + lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; + } + if (options != null) { + final boolean useLockTask = options.getLockTaskMode(); if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) { lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; } - // Gets launch display id from options. It returns INVALID_DISPLAY if not set. - mHandoverLaunchDisplayId = options.getLaunchDisplayId(); } + return lockTaskLaunchMode; } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index d5961a881c36..d380f8cd7337 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -356,6 +356,8 @@ class ActivityStack extends Task implements BoundsAnimationTarget { // TODO(task-hierarchy): remove when tiles can be actual parents TaskTile mTile = null; + private int mLastTaskOrganizerWindowingMode = -1; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -794,6 +796,13 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } final int windowingMode = getWindowingMode(); + if (windowingMode == mLastTaskOrganizerWindowingMode) { + // If our windowing mode hasn't actually changed, then just stick + // with our old organizer. This lets us implement the semantic + // where SysUI can continue to manage it's old tasks + // while CTS temporarily takes over the registration. + return; + } /* * Different windowing modes may be managed by different task organizers. If * getTaskOrganizer returns null, we still call setTaskOrganizer to @@ -802,6 +811,7 @@ class ActivityStack extends Task implements BoundsAnimationTarget { final ITaskOrganizer org = mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); setTaskOrganizer(org); + mLastTaskOrganizerWindowingMode = windowingMode; } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 75d87edbc437..f35ba9e69ed7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -386,6 +386,8 @@ public class ActivityStartController { } else { callingPid = callingUid = -1; } + final int filterCallingUid = ActivityStarter.computeResolveFilterUid( + callingUid, realCallingUid, UserHandle.USER_NULL); final SparseArray<String> startingUidPkgs = new SparseArray<>(); final long origId = Binder.clearCallingIdentity(); try { @@ -408,9 +410,7 @@ public class ActivityStartController { // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], - 0 /* startFlags */, null /* profilerInfo */, userId, - ActivityStarter.computeResolveFilterUid( - callingUid, realCallingUid, UserHandle.USER_NULL)); + 0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid); aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId); if (aInfo != null) { @@ -457,6 +457,7 @@ public class ActivityStartController { Slog.wtf(TAG, sb.toString()); } + final IBinder sourceResultTo = resultTo; final ActivityRecord[] outActivity = new ActivityRecord[1]; // Lock the loop to ensure the activities launched in a sequence. synchronized (mService.mGlobalLock) { @@ -470,7 +471,18 @@ public class ActivityStartController { } return startResult; } - resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + final ActivityRecord started = outActivity[0]; + if (started != null && started.getUid() == filterCallingUid) { + // Only the started activity which has the same uid as the source caller can + // be the caller of next activity. + resultTo = started.appToken; + } else { + resultTo = sourceResultTo; + // Different apps not adjacent to the caller are forced to be new task. + if (i < starters.length - 1) { + starters[i + 1].getIntent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + } } } } finally { diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 2fb0ac5fbeaa..76aa1d115ef6 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -52,6 +52,7 @@ import android.os.UserHandle; import android.os.UserManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.BlockedAppActivity; import com.android.internal.app.HarmfulAppWarningActivity; import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; @@ -166,6 +167,9 @@ class ActivityStartInterceptor { // no user action can undo this. return true; } + if (interceptLockTaskModeViolationPackageIfNeeded()) { + return true; + } if (interceptHarmfulAppIfNeeded()) { // If the app has a "harmful app" warning associated with it, we should ask to uninstall // before issuing the work challenge. @@ -270,6 +274,25 @@ class ActivityStartInterceptor { return true; } + private boolean interceptLockTaskModeViolationPackageIfNeeded() { + if (mAInfo == null || mAInfo.applicationInfo == null) { + return false; + } + LockTaskController controller = mService.getLockTaskController(); + String packageName = mAInfo.applicationInfo.packageName; + int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions); + if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) { + return false; + } + mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName); + mCallingPid = mRealCallingPid; + mCallingUid = mRealCallingUid; + mResolvedType = null; + mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid); + mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); + return true; + } + private boolean interceptWorkProfileChallengeIfNeeded() { final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId); if (interceptingIntent == null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 9e3292b59402..c7270f257923 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2493,7 +2493,6 @@ class ActivityStarter { return this; } - @VisibleForTesting Intent getIntent() { return mRequest.intent; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java new file mode 100644 index 000000000000..94decc792fd3 --- /dev/null +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.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.server.wm; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; + +import java.util.HashMap; + +/** + * Utility class for collecting and merging transactions from various sources asynchronously. + * For example to use to synchronously resize all the children of a window container + * 1. Open a new sync set, and pass the listener that will be invoked + * int id startSyncSet(TransactionReadyListener) + * the returned ID will be eventually passed to the TransactionReadyListener in combination + * with the prepared transaction. You also use it to refer to the operation in future steps. + * 2. Ask each child to participate: + * addToSyncSet(int id, WindowContainer wc) + * if the child thinks it will be affected by a configuration change (a.k.a. has a visible + * window in its sub hierarchy, then we will increment a counter of expected callbacks + * At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy + * updates in to the sync engine. + * 3. Apply your configuration changes to the window containers. + * 4. Tell the engine that the sync set is ready + * setReady(int id) + * 5. If there were no sub windows anywhere in the hierarchy to wait on, then + * transactionReady is immediately invoked, otherwise all the windows are poked + * to redraw and to deliver a buffer to WMS#finishDrawing. + * Once all this drawing is complete the combined transaction of all the buffers + * and pending transaction hierarchy changes will be delivered to the TransactionReadyListener + */ +class BLASTSyncEngine { + private static final String TAG = "BLASTSyncEngine"; + + interface TransactionReadyListener { + void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction); + }; + + // Holds state associated with a single synchronous set of operations. + class SyncState implements TransactionReadyListener { + int mSyncId; + SurfaceControl.Transaction mMergedTransaction; + int mRemainingTransactions; + TransactionReadyListener mListener; + boolean mReady = false; + + private void tryFinish() { + if (mRemainingTransactions == 0 && mReady) { + mListener.transactionReady(mSyncId, mMergedTransaction); + mPendingSyncs.remove(mSyncId); + } + } + + public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + mRemainingTransactions--; + mMergedTransaction.merge(mergedTransaction); + tryFinish(); + } + + void setReady() { + mReady = true; + tryFinish(); + } + + boolean addToSync(WindowContainer wc) { + if (wc.prepareForSync(this, mSyncId)) { + mRemainingTransactions++; + return true; + } + return false; + } + + SyncState(TransactionReadyListener l, int id) { + mListener = l; + mSyncId = id; + mMergedTransaction = new SurfaceControl.Transaction(); + mRemainingTransactions = 0; + } + }; + + int mNextSyncId = 0; + + final HashMap<Integer, SyncState> mPendingSyncs = new HashMap(); + + BLASTSyncEngine() { + } + + int startSyncSet(TransactionReadyListener listener) { + final int id = mNextSyncId++; + final SyncState s = new SyncState(listener, id); + mPendingSyncs.put(id, s); + return id; + } + + boolean addToSyncSet(int id, WindowContainer wc) { + final SyncState st = mPendingSyncs.get(id); + return st.addToSync(wc); + } + + // TODO(b/148476626): TIMEOUTS! + void setReady(int id) { + final SyncState st = mPendingSyncs.get(id); + st.setReady(); + } +} diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index efe79b36d645..e71371a477f7 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -526,7 +526,9 @@ public class DisplayRotation { mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; mDisplayContent.sendNewConfiguration(); - mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + if (t != null) { + mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); + } } } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 02413bb48518..3b25b742e327 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -23,6 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.content.Context.STATUS_BAR_SERVICE; import static android.content.Intent.ACTION_CALL_EMERGENCY; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_CURRENT; import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; @@ -339,6 +341,25 @@ public class LockTaskController { & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0; } + private boolean isBlockingInTaskEnabled(int userId) { + return (getLockTaskFeaturesForUser(userId) + & DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) != 0; + } + + boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) { + if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED || !isBlockingInTaskEnabled(userId)) { + return true; + } + switch (lockTaskLaunchMode) { + case LOCK_TASK_LAUNCH_MODE_ALWAYS: + return true; + case LOCK_TASK_LAUNCH_MODE_NEVER: + return false; + default: + } + return isPackageWhitelisted(userId, packageName); + } + private boolean isEmergencyCallTask(Task task) { final Intent intent = task.intent; if (intent == null) { diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 44a6fc936961..096541f57ba5 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -38,6 +38,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.ITaskOrganizer; import android.view.IWindowContainer; +import android.view.SurfaceControl; import android.view.WindowContainerTransaction; import com.android.internal.util.function.pooled.PooledConsumer; @@ -53,7 +54,8 @@ import java.util.WeakHashMap; * Stores the TaskOrganizers associated with a given windowing mode and * their associated state. */ -class TaskOrganizerController extends ITaskOrganizerController.Stub { +class TaskOrganizerController extends ITaskOrganizerController.Stub + implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "TaskOrganizerController"; /** Flag indicating that an applied transaction may have effected lifecycle */ @@ -74,11 +76,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @Override public void binderDied() { synchronized (mGlobalLock) { - final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer); - for (int i = 0; i < state.mOrganizedTasks.size(); i++) { - state.mOrganizedTasks.get(i).taskOrganizerDied(); - } - mTaskOrganizerStates.remove(mTaskOrganizer); + final TaskOrganizerState state = + mTaskOrganizerStates.get(mTaskOrganizer.asBinder()); + state.releaseTasks(); + mTaskOrganizerStates.remove(mTaskOrganizer.asBinder()); if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) { mTaskOrganizersForWindowingMode.remove(mWindowingMode); } @@ -89,32 +90,84 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { class TaskOrganizerState { ITaskOrganizer mOrganizer; DeathRecipient mDeathRecipient; + int mWindowingMode; ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + // Save the TaskOrganizer which we replaced registration for + // so it can be re-registered if we unregister. + TaskOrganizerState mReplacementFor; + boolean mDisposed = false; + + + TaskOrganizerState(ITaskOrganizer organizer, int windowingMode, + TaskOrganizerState replacing) { + mOrganizer = organizer; + mDeathRecipient = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } + mWindowingMode = windowingMode; + mReplacementFor = replacing; + } + void addTask(Task t) { mOrganizedTasks.add(t); + try { + mOrganizer.taskAppeared(t.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } } void removeTask(Task t) { + try { + mOrganizer.taskVanished(t.getRemoteToken()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } mOrganizedTasks.remove(t); } - TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) { - mOrganizer = organizer; - mDeathRecipient = deathRecipient; + void dispose() { + mDisposed = true; + releaseTasks(); + handleReplacement(); + } + + void releaseTasks() { + for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) { + final Task t = mOrganizedTasks.get(i); + t.taskOrganizerDied(); + removeTask(t); + } + } + + void handleReplacement() { + if (mReplacementFor != null && !mReplacementFor.mDisposed) { + mTaskOrganizersForWindowingMode.put(mWindowingMode, mReplacementFor); + } + } + + void unlinkDeath() { + mDisposed = true; + mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0); } }; final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap(); - final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); + final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>(); + private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); + final ActivityTaskManagerService mService; RunningTaskInfo mTmpTaskInfo; @@ -128,17 +181,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } - private void clearIfNeeded(int windowingMode) { - final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode); - if (oldState != null) { - oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0); - } - } - /** * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. * If there was already a TaskOrganizer for this windowing mode it will be evicted - * and receive taskVanished callbacks in the process. + * but will continue to organize it's existing tasks. */ @Override public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { @@ -153,24 +199,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - clearIfNeeded(windowingMode); - DeathRecipient dr = new DeathRecipient(organizer, windowingMode); - try { - organizer.asBinder().linkToDeath(dr, 0); - } catch (RemoteException e) { - Slog.e(TAG, "TaskOrganizer failed to register death recipient"); - } - - final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + final TaskOrganizerState state = new TaskOrganizerState(organizer, windowingMode, + mTaskOrganizersForWindowingMode.get(windowingMode)); mTaskOrganizersForWindowingMode.put(windowingMode, state); - - mTaskOrganizerStates.put(organizer, state); + mTaskOrganizerStates.put(organizer.asBinder(), state); } } finally { Binder.restoreCallingIdentity(origId); } } + void unregisterTaskOrganizer(ITaskOrganizer organizer) { + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); + state.unlinkDeath(); + if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) { + mTaskOrganizersForWindowingMode.remove(state.mWindowingMode); + } + state.dispose(); + } + ITaskOrganizer getTaskOrganizer(int windowingMode) { final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode); if (state == null) { @@ -179,35 +226,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return state.mOrganizer; } - private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { - try { - organizer.taskAppeared(task.getTaskInfo()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskAppeared callback" + e); - } - } - - private void sendTaskVanished(ITaskOrganizer organizer, Task task) { - try { - organizer.taskVanished(task.getRemoteToken()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskVanished callback" + e); - } - } - void onTaskAppeared(ITaskOrganizer organizer, Task task) { - TaskOrganizerState state = mTaskOrganizerStates.get(organizer); - + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); state.addTask(task); - sendTaskAppeared(organizer, task); } void onTaskVanished(ITaskOrganizer organizer, Task task) { - final TaskOrganizerState state = mTaskOrganizerStates.get(organizer); - sendTaskVanished(organizer, task); - - // This could trigger TaskAppeared for other tasks in the same stack so make sure - // we do this AFTER sending taskVanished. + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); state.removeTask(task); } @@ -408,15 +433,35 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void applyContainerTransaction(WindowContainerTransaction t) { + public int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) { enforceStackPermission("applyContainerTransaction()"); + int syncId = -1; if (t == null) { - return; + throw new IllegalArgumentException( + "Null transaction passed to applyContainerTransaction"); } long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { int effects = 0; + + /** + * If organizer is non-null we are looking to synchronize this transaction + * by collecting all the results in to a SurfaceFlinger transaction and + * then delivering that to the given organizers transaction ready callback. + * See {@link BLASTSyncEngine} for the details of the operation. But at + * a high level we create a sync operation with a given ID and an associated + * organizer. Then we notify each WindowContainer in this WindowContainer + * transaction that it is participating in a sync operation with that + * ID. Once everything is notified we tell the BLASTSyncEngine + * "setSyncReady" which means that we have added everything + * to the set. At any point after this, all the WindowContainers + * will eventually finish applying their changes and notify the + * BLASTSyncEngine which will deliver the Transaction to the organizer. + */ + if (organizer != null) { + syncId = startSyncWithOrganizer(organizer); + } mService.deferWindowLayout(); try { ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); @@ -429,11 +474,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { entry.getKey()).getContainer(); int containerEffect = applyWindowContainerChange(wc, entry.getValue()); effects |= containerEffect; + // Lifecycle changes will trigger ensureConfig for everything. if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { haveConfigChanges.add(wc); } + if (syncId >= 0) { + mBLASTSyncEngine.addToSyncSet(syncId, wc); + } } if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { // Already calls ensureActivityConfig @@ -454,10 +503,38 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } finally { mService.continueWindowLayout(); + if (syncId >= 0) { + setSyncReady(syncId); + } } } } finally { Binder.restoreCallingIdentity(ident); } + return syncId; + } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction sc) { + final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id); + if (organizer == null) { + Slog.e(TAG, "Got transaction complete for unexpected ID"); + } + try { + organizer.transactionReady(id, sc); + } catch (RemoteException e) { + } + + mTaskOrganizersByPendingSyncId.remove(id); + } + + int startSyncWithOrganizer(ITaskOrganizer organizer) { + int id = mBLASTSyncEngine.startSyncSet(this); + mTaskOrganizersByPendingSyncId.put(id, organizer); + return id; + } + + void setSyncReady(int id) { + mBLASTSyncEngine.setReady(id); } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 86723153c5f6..9acb660967cb 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -94,7 +94,8 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E> - implements Comparable<WindowContainer>, Animatable { + implements Comparable<WindowContainer>, Animatable, + BLASTSyncEngine.TransactionReadyListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -260,6 +261,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ RemoteToken mRemoteToken = null; + BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); + SurfaceControl.Transaction mBLASTSyncTransaction = new SurfaceControl.Transaction(); + boolean mUsingBLASTSyncTransaction = false; + BLASTSyncEngine.TransactionReadyListener mWaitingListener; + int mWaitingSyncId; + WindowContainer(WindowManagerService wms) { mWmService = wms; mPendingTransaction = wms.mTransactionFactory.get(); @@ -1837,6 +1844,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public Transaction getPendingTransaction() { + if (mUsingBLASTSyncTransaction) { + return mBLASTSyncTransaction; + } + final DisplayContent displayContent = getDisplayContent(); if (displayContent != null && displayContent != this) { return displayContent.getPendingTransaction(); @@ -2316,4 +2327,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return sb.toString(); } } + + @Override + public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + mergedTransaction.merge(mBLASTSyncTransaction); + mUsingBLASTSyncTransaction = false; + + mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction); + + mWaitingListener = null; + mWaitingSyncId = -1; + } + + boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, + int waitingId) { + boolean willSync = false; + if (!isVisible()) { + return willSync; + } + mUsingBLASTSyncTransaction = true; + + int localId = mBLASTSyncEngine.startSyncSet(this); + for (int i = 0; i < mChildren.size(); i++) { + final WindowContainer child = mChildren.get(i); + willSync = mBLASTSyncEngine.addToSyncSet(localId, child) | willSync; + } + + // Make sure to set these before we call setReady in case the sync was a no-op + mWaitingSyncId = waitingId; + mWaitingListener = waitingListener; + + mBLASTSyncEngine.setReady(localId); + + return willSync; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 6e243f0b937a..59eee9cc9404 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -562,4 +562,14 @@ public abstract class WindowManagerInternal { */ public abstract void setAccessibilityIdToSurfaceMetadata( IBinder windowToken, int accessibilityWindowId); + + /** + * Transfers input focus from a given input token to that of the IME window. + * + * @param sourceInputToken The source token. + * @param displayId The display hosting the IME window. + * @return Whether transfer was successful. + */ + public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, + int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a5b99b0a26a3..633098566b1b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2505,7 +2505,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = windowForClientLocked(session, client, false); ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s", win, (win != null ? win.mWinAnimator.drawStateToString() : "null")); - if (win != null && win.mWinAnimator.finishDrawingLocked(postDrawTransaction)) { + if (win != null && win.finishDrawing(postDrawTransaction)) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { win.getDisplayContent().pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -5687,6 +5687,12 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setForceShowSystemBars(boolean show) { + boolean isAutomotive = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + if (!isAutomotive) { + throw new UnsupportedOperationException("Force showing system bars is only supported" + + "for Automotive use cases."); + } if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " @@ -7558,6 +7564,29 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + @Override + public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, + int displayId) { + final IBinder destinationInputToken; + + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + return false; + } + final WindowState imeWindow = displayContent.mInputMethodWindow; + if (imeWindow == null) { + return false; + } + if (imeWindow.mInputChannel == null) { + return false; + } + destinationInputToken = imeWindow.mInputChannel.getToken(); + } + + return mInputManager.transferTouchFocus(sourceInputToken, destinationInputToken); + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 42e5bbc1c1ee..b336f8d1a1ac 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5689,4 +5689,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP SurfaceControl getDeferTransactionBarrier() { return mWinAnimator.getDeferTransactionBarrier(); } + + @Override + boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, + int waitingId) { + // TODO(b/148871522): Support child window + mWaitingListener = waitingListener; + mWaitingSyncId = waitingId; + mUsingBLASTSyncTransaction = true; + return true; + } + + boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) { + if (!mUsingBLASTSyncTransaction) { + return mWinAnimator.finishDrawingLocked(postDrawTransaction); + } + if (postDrawTransaction == null) { + postDrawTransaction = new SurfaceControl.Transaction(); + } + postDrawTransaction.merge(mBLASTSyncTransaction); + mWaitingListener.transactionReady(mWaitingSyncId, postDrawTransaction); + mUsingBLASTSyncTransaction = false; + + mWaitingSyncId = 0; + mWaitingListener = null; + return mWinAnimator.finishDrawingLocked(null); + } } diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java index 9f307bb0f98f..59abaab0f795 100644 --- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java +++ b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java @@ -59,7 +59,7 @@ public class DisplayRotationUtil { } /** - * Compute bounds after rotating teh screen. + * Compute bounds after rotating the screen. * * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements. * @param rotation rotation constant defined in android.view.Surface. diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 390068e1fa75..812bc438246f 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -109,6 +109,7 @@ cc_defaults { "libinputservice", "libprotobuf-cpp-lite", "libprotoutil", + "libstatshidl", "libstatspull", "libstatssocket", "libstatslog", @@ -155,6 +156,7 @@ cc_defaults { "android.hardware.vr@1.0", "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", + "android.frameworks.stats@1.0", "android.system.suspend@1.0", "service.incremental", "suspend_control_aidl_interface-cpp", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index 7644adebb10a..aa7067ee7509 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -137,7 +137,7 @@ using namespace google::protobuf; #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 #define FRAME_COUNTS_FIELD_NUMBER 2 -static void writeCpuHistogram(stats_event* event, +static void writeCpuHistogram(AStatsEvent* event, const uirenderer::protos::GraphicsStatsProto& stat) { util::ProtoOutputStream proto; for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { @@ -154,10 +154,10 @@ static void writeCpuHistogram(stats_event* event, } std::vector<uint8_t> outVector; proto.serializeToVector(&outVector); - stats_event_write_byte_array(event, outVector.data(), outVector.size()); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); } -static void writeGpuHistogram(stats_event* event, +static void writeGpuHistogram(AStatsEvent* event, const uirenderer::protos::GraphicsStatsProto& stat) { util::ProtoOutputStream proto; for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { @@ -174,20 +174,20 @@ static void writeGpuHistogram(stats_event* event, } std::vector<uint8_t> outVector; proto.serializeToVector(&outVector); - stats_event_write_byte_array(event, outVector.data(), outVector.size()); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); } // graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. -static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { +static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { JNIEnv* env = getJNIEnv(); if (!env) { return false; } if (gGraphicsStatsServiceObject == nullptr) { ALOGE("Failed to get graphicsstats service"); - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } for (bool lastFullDay : {true, false}) { @@ -199,7 +199,7 @@ static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag, env->ExceptionDescribe(); env->ExceptionClear(); ALOGE("Failed to invoke graphicsstats service"); - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } if (!jdata) { // null means data is not available for that day. @@ -218,49 +218,51 @@ static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag, if (!success) { ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", serviceDump.InitializationErrorString().c_str(), dataSize); - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { auto& stat = serviceDump.stats(stat_index); - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); - stats_event_write_string8(event, stat.package_name().c_str()); - stats_event_write_int64(event, (int64_t)stat.version_code()); - stats_event_write_int64(event, (int64_t)stat.stats_start()); - stats_event_write_int64(event, (int64_t)stat.stats_end()); - stats_event_write_int32(event, (int32_t)stat.pipeline()); - stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); - stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); - stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); - stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS); + AStatsEvent_writeString(event, stat.package_name().c_str()); + AStatsEvent_writeInt64(event, (int64_t)stat.version_code()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_start()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_end()); + AStatsEvent_writeInt32(event, (int32_t)stat.pipeline()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count()); writeCpuHistogram(event, stat); writeGpuHistogram(event, stat); // TODO: fill in UI mainline module version, when the feature is available. - stats_event_write_int64(event, (int64_t)0); - stats_event_write_bool(event, !lastFullDay); - stats_event_build(event); + AStatsEvent_writeInt64(event, (int64_t)0); + AStatsEvent_writeBool(event, !lastFullDay); + AStatsEvent_build(event); } } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } // Register a puller for GRAPHICS_STATS atom with the statsd service. static void nativeInit(JNIEnv* env, jobject javaObject) { gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); - pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds - .timeout_ns = 2 * NS_PER_SEC, // 2 seconds - .additive_fields = nullptr, - .additive_fields_size = 0}; - register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, - &metadata, nullptr); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000); // 10 milliseconds + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds + + AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS, + &graphicsStatsPullCallback, metadata, nullptr); + + AStatsManager_PullAtomMetadata_release(metadata); } static void nativeDestructor(JNIEnv* env, jobject javaObject) { - //TODO: Unregister the puller callback when a new API is available. + AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS); env->DeleteGlobalRef(gGraphicsStatsServiceObject); gGraphicsStatsServiceObject = nullptr; } diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 67254b811ee0..279ea4b9a790 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -29,6 +29,7 @@ #include <schedulerservice/SchedulingPolicyService.h> #include <sensorservice/SensorService.h> #include <sensorservicehidl/SensorManager.h> +#include <stats/StatsHal.h> #include <bionic/malloc.h> #include <bionic/reserved_signals.h> @@ -59,6 +60,8 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject / using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService; using ::android::frameworks::sensorservice::V1_0::ISensorManager; using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager; + using ::android::frameworks::stats::V1_0::IStats; + using ::android::frameworks::stats::V1_0::implementation::StatsHal; using ::android::hardware::configureRpcThreadpool; status_t err; @@ -75,6 +78,10 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject / sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService(); err = schedulingService->registerAsService(); ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err); + + sp<IStats> statsHal = new StatsHal(); + err = statsHal->registerAsService(); + ALOGE_IF(err != OK, "Cannot register %s: %d", IStats::descriptor, err); } static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 212a3a638634..49db3d5b2b64 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1563,20 +1563,17 @@ static void nativeSetSystemUiVisibility(JNIEnv* /* env */, } static jboolean nativeTransferTouchFocus(JNIEnv* env, - jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) { - NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - - sp<InputChannel> fromChannel = - android_view_InputChannel_getInputChannel(env, fromChannelObj); - sp<InputChannel> toChannel = - android_view_InputChannel_getInputChannel(env, toChannelObj); - - if (fromChannel == nullptr || toChannel == nullptr) { + jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) { + if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) { return JNI_FALSE; } + sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj); + sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj); + + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); if (im->getInputManager()->getDispatcher()->transferTouchFocus( - fromChannel->getConnectionToken(), toChannel->getConnectionToken())) { + fromChannelToken, toChannelToken)) { return JNI_TRUE; } else { return JNI_FALSE; @@ -1784,7 +1781,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeSetInputDispatchMode }, { "nativeSetSystemUiVisibility", "(JI)V", (void*) nativeSetSystemUiVisibility }, - { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z", + { "nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z", (void*) nativeTransferTouchFocus }, { "nativeSetPointerSpeed", "(JI)V", (void*) nativeSetPointerSpeed }, diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp index f5b778e85e5c..43cd0a2d2474 100644 --- a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp +++ b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp @@ -31,32 +31,32 @@ namespace android { static server::stats::PowerStatsPuller gPowerStatsPuller; static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller; -static status_pull_atom_return_t onDevicePowerMeasurementCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { +static AStatsManager_PullAtomCallbackReturn onDevicePowerMeasurementCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { return gPowerStatsPuller.Pull(atom_tag, data); } -static status_pull_atom_return_t subsystemSleepStateCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { +static AStatsManager_PullAtomCallbackReturn subsystemSleepStateCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { return gSubsystemSleepStatePuller.Pull(atom_tag, data); } static void nativeInit(JNIEnv* env, jobject javaObject) { // on device power measurement gPowerStatsPuller = server::stats::PowerStatsPuller(); - register_stats_pull_atom_callback(android::util::ON_DEVICE_POWER_MEASUREMENT, - onDevicePowerMeasurementCallback, - /* metadata= */ nullptr, - /* cookie= */ nullptr); + AStatsManager_registerPullAtomCallback(android::util::ON_DEVICE_POWER_MEASUREMENT, + onDevicePowerMeasurementCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); // subsystem sleep state gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller(); - register_stats_pull_atom_callback(android::util::SUBSYSTEM_SLEEP_STATE, - subsystemSleepStateCallback, - /* metadata= */ nullptr, - /* cookie= */ nullptr); + AStatsManager_registerPullAtomCallback(android::util::SUBSYSTEM_SLEEP_STATE, + subsystemSleepStateCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); } static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}}; diff --git a/services/core/jni/stats/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp index e80b5cfc4a71..d8f6faac0ffb 100644 --- a/services/core/jni/stats/PowerStatsPuller.cpp +++ b/services/core/jni/stats/PowerStatsPuller.cpp @@ -78,11 +78,12 @@ static bool getPowerStatsHalLocked() { PowerStatsPuller::PowerStatsPuller() {} -status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_event_list* data) { +AStatsManager_PullAtomCallbackReturn PowerStatsPuller::Pull(int32_t atomTag, + AStatsEventList* data) { std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); if (!getPowerStatsHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Pull getRailInfo if necessary @@ -100,14 +101,14 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again. if (gRailInfo.empty()) { ALOGE("power.stats has no rail information"); gPowerStatsExist = false; // No rail info, so never try again. gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } } @@ -134,15 +135,16 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e } const RailInfo& rail = gRailInfo[energyData.index]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, - android::util::ON_DEVICE_POWER_MEASUREMENT); - stats_event_write_string8(event, - rail.subsysName.c_str()); - stats_event_write_string8(event, rail.railName.c_str()); - stats_event_write_int64(event, energyData.timestamp); - stats_event_write_int64(event, energyData.energy); - stats_event_build(event); + AStatsEvent* event = + AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId( + event, + android::util::ON_DEVICE_POWER_MEASUREMENT); + AStatsEvent_writeString(event, rail.subsysName.c_str()); + AStatsEvent_writeString(event, rail.railName.c_str()); + AStatsEvent_writeInt64(event, energyData.timestamp); + AStatsEvent_writeInt64(event, energyData.energy); + AStatsEvent_build(event); ALOGV("power.stat: %s.%s: %llu, %llu", rail.subsysName.c_str(), rail.railName.c_str(), @@ -153,9 +155,9 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } } // namespace stats diff --git a/services/core/jni/stats/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h index 048dbb933f52..db07d600d251 100644 --- a/services/core/jni/stats/PowerStatsPuller.h +++ b/services/core/jni/stats/PowerStatsPuller.h @@ -29,7 +29,7 @@ namespace stats { class PowerStatsPuller { public: PowerStatsPuller(); - status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); + AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data); }; } // namespace stats diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp index c6a836cfb6d5..45afb5eb7aa9 100644 --- a/services/core/jni/stats/SubsystemSleepStatePuller.cpp +++ b/services/core/jni/stats/SubsystemSleepStatePuller.cpp @@ -55,7 +55,7 @@ namespace android { namespace server { namespace stats { -static std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> +static std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)> gPuller = {}; static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr; @@ -176,12 +176,12 @@ static bool getPowerStatsHalLocked() { } // The caller must be holding gPowerHalMutex. -static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, - pulled_stats_event_list* data) { +static AStatsManager_PullAtomCallbackReturn getIPowerStatsDataLocked(int32_t atomTag, + AStatsEventList* data) { using android::hardware::power::stats::V1_0::Status; if(!getPowerStatsHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Get power entity state residency data bool success = false; @@ -194,17 +194,17 @@ static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, } for (auto result : results) { for (auto stateResidency : result.stateResidencyData) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, - gEntityNames.at(result.powerEntityId).c_str()); - stats_event_write_string8(event, - gStateNames.at(result.powerEntityId) - .at(stateResidency.powerEntityStateId) - .c_str()); - stats_event_write_int64(event, stateResidency.totalStateEntryCount); - stats_event_write_int64(event, stateResidency.totalTimeInStateMs); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, + gEntityNames.at(result.powerEntityId).c_str()); + AStatsEvent_writeString(event, + gStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId) + .c_str()); + AStatsEvent_writeInt64(event, stateResidency.totalStateEntryCount); + AStatsEvent_writeInt64(event, stateResidency.totalTimeInStateMs); + AStatsEvent_build(event); } } success = true; @@ -213,9 +213,9 @@ static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, // bool success determines if this succeeded or not. checkResultLocked(ret, __func__); if (!success) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. @@ -244,12 +244,12 @@ static bool getPowerHalLocked() { } // The caller must be holding gPowerHalMutex. -static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, - pulled_stats_event_list* data) { +static AStatsManager_PullAtomCallbackReturn getIPowerDataLocked(int32_t atomTag, + AStatsEventList* data) { using android::hardware::power::V1_0::Status; if(!getPowerHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } Return<void> ret; @@ -259,26 +259,26 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, for (size_t i = 0; i < states.size(); i++) { const PowerStatePlatformSleepState& state = states[i]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_string8(event, ""); - stats_event_write_int64(event, state.totalTransitions); - stats_event_write_int64(event, state.residencyInMsecSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeString(event, ""); + AStatsEvent_writeInt64(event, state.totalTransitions); + AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot); + AStatsEvent_build(event); ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(), (long long)state.residencyInMsecSinceBoot, (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0); for (const auto& voter : state.voters) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_string8(event, voter.name.c_str()); - stats_event_write_int64(event, voter.totalNumberOfTimesVotedSinceBoot); - stats_event_write_int64(event, voter.totalTimeInMsecVotedForSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeString(event, voter.name.c_str()); + AStatsEvent_writeInt64(event, voter.totalNumberOfTimesVotedSinceBoot); + AStatsEvent_writeInt64(event, voter.totalTimeInMsecVotedForSinceBoot); + AStatsEvent_build(event); ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), voter.name.c_str(), @@ -288,7 +288,7 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, } }); if (!checkResultLocked(ret, __func__)) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1 @@ -305,14 +305,14 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, for (size_t j = 0; j < subsystem.states.size(); j++) { const PowerStateSubsystemSleepState& state = subsystem.states[j]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, - android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, subsystem.name.c_str()); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_int64(event, state.totalTransitions); - stats_event_write_int64(event, state.residencyInMsecSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, + android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, subsystem.name.c_str()); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeInt64(event, state.totalTransitions); + AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot); + AStatsEvent_build(event); ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld", subsystem.name.c_str(), state.name.c_str(), @@ -324,14 +324,14 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, } }); } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. -std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> +std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)> getPullerLocked() { - std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list * data)> ret = - {}; + std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList * data)> + ret = {}; // First see if power.stats HAL is available. Fall back to power HAL if // power.stats HAL is unavailable. @@ -346,8 +346,8 @@ getPullerLocked() { return ret; } -status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag, - pulled_stats_event_list* data) { +AStatsManager_PullAtomCallbackReturn SubsystemSleepStatePuller::Pull(int32_t atomTag, + AStatsEventList* data) { std::lock_guard<std::mutex> lock(gPowerHalMutex); if(!gPuller) { @@ -359,7 +359,7 @@ status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag, } ALOGE("Unable to load Power Hal or power.stats HAL"); - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } } // namespace stats diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h index 59dbbd258401..da9679c68a64 100644 --- a/services/core/jni/stats/SubsystemSleepStatePuller.h +++ b/services/core/jni/stats/SubsystemSleepStatePuller.h @@ -29,7 +29,7 @@ namespace stats { class SubsystemSleepStatePuller { public: SubsystemSleepStatePuller(); - status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); + AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data); }; } // namespace stats diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 65cabadaa3d8..553ec4201cc2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4680,12 +4680,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void ensureMinimumQuality( int userId, ActiveAdmin admin, int minimumQuality, String operation) { - if (admin.mPasswordPolicy.quality < minimumQuality - && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), - userId)) { - throw new IllegalStateException(String.format( - "password quality should be at least %d for %s", minimumQuality, operation)); - } + mInjector.binderWithCleanCallingIdentity(() -> { + if (admin.mPasswordPolicy.quality < minimumQuality + && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), + userId)) { + throw new IllegalStateException(String.format( + "password quality should be at least %d for %s", + minimumQuality, operation)); + } + }); } @Override @@ -5343,7 +5346,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked( userHandle, parent); - return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL; + return admin != null ? getUserIdToWipeForFailedPasswords(admin) : UserHandle.USER_NULL; } } @@ -5354,7 +5357,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * <li>this user and all profiles that don't have their own challenge otherwise. * </ul> * <p>If the policy for the primary and any other profile are equal, it returns the admin for - * the primary profile. + * the primary profile. Policy of a PO on an organization-owned device applies to the primary + * profile. * Returns {@code null} if no participating admin has that policy set. */ private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked( @@ -5373,7 +5377,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // We always favor the primary profile if several profiles have the same value set. - int userId = admin.getUserHandle().getIdentifier(); + final int userId = getUserIdToWipeForFailedPasswords(admin); if (count == 0 || count > admin.maximumFailedPasswordsForWipe || (count == admin.maximumFailedPasswordsForWipe && @@ -7170,7 +7174,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (wipeData && strictestAdmin != null) { - final int userId = strictestAdmin.getUserHandle().getIdentifier(); + final int userId = getUserIdToWipeForFailedPasswords(strictestAdmin); Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: " + strictestAdmin.info.getComponent().flattenToShortString() + ". Calling wipeData for user " + userId); @@ -7201,6 +7205,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /** + * Returns which user should be wiped if this admin's maximum filed password attempts policy is + * violated. + */ + private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) { + final int userId = admin.getUserHandle().getIdentifier(); + final ComponentName component = admin.info.getComponent(); + return isProfileOwnerOfOrganizationOwnedDevice(component, userId) + ? getProfileParentId(userId) : userId; + } + @Override public void reportSuccessfulPasswordAttempt(int userHandle) { enforceFullCrossUsersPermission(userHandle); @@ -11583,6 +11598,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { millis, "DevicePolicyManagerService: setTime"); mInjector.binderWithCleanCallingIdentity( () -> mInjector.getTimeDetector().suggestManualTime(manualTimeSuggestion)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_TIME) + .setAdmin(who) + .write(); return true; } @@ -11599,6 +11619,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { timeZone, "DevicePolicyManagerService: setTimeZone"); mInjector.binderWithCleanCallingIdentity(() -> mInjector.getTimeZoneDetector().suggestManualTimeZone(manualTimeZoneSuggestion)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_TIME_ZONE) + .setAdmin(who) + .write(); return true; } diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index a94a75a26875..8a099776b54b 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -20,12 +20,12 @@ #include <binder/IServiceManager.h> #include "IncrementalService.h" -#include "android/os/incremental/BnIncrementalManagerNative.h" +#include "android/os/incremental/BnIncrementalService.h" #include "incremental_service.h" namespace android::os::incremental { -class BinderIncrementalService : public BnIncrementalManagerNative, +class BinderIncrementalService : public BnIncrementalService, public BinderService<BinderIncrementalService> { public: BinderIncrementalService(const sp<IServiceManager> &sm); diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java index d825b6b2bd8f..45e0aac24ca7 100644 --- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java +++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java @@ -107,7 +107,7 @@ class CallLogQueryHelper { } @Event.EventType int eventType = CALL_TYPE_TO_EVENT_TYPE.get(callType); Event event = new Event.Builder(date, eventType) - .setCallDetails(new Event.CallDetails(durationSeconds)) + .setDurationSeconds((int) durationSeconds) .build(); mEventConsumer.accept(phoneNumber, event); return true; 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 f17e1b91cb5d..364992181f75 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -40,6 +40,9 @@ class ConversationStore { // Phone Number -> Shortcut ID private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); + // Notification Channel ID -> Shortcut ID + private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); + void addOrUpdate(@NonNull ConversationInfo conversationInfo) { mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); @@ -57,6 +60,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + } } void deleteConversation(@NonNull String shortcutId) { @@ -79,6 +87,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.remove(phoneNumber); } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.remove(notifChannelId); + } } void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { @@ -106,4 +119,9 @@ class ConversationStore { ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } + + @Nullable + ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { + return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); + } } 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 79503f797318..7fdcf42c6364 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -24,8 +24,6 @@ import android.app.Notification; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; -import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -69,6 +67,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; /** * A class manages the lifecycle of the conversations and associated data, and exposes the methods @@ -96,7 +95,6 @@ public class DataManager { private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; - private UsageStatsManagerInternal mUsageStatsManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; @@ -118,7 +116,6 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); - mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); @@ -293,13 +290,14 @@ public class DataManager { | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; return mShortcutServiceInternal.getShortcuts( mInjector.getCallingUserId(), /*callingPackage=*/ PLATFORM_PACKAGE_NAME, - /*changedSince=*/ 0, packageName, shortcutIds, /*componentName=*/ null, queryFlags, - userId, MY_PID, MY_UID); + /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null, + /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID); } private void forAllUnlockedUsers(Consumer<UserData> consumer) { for (int i = 0; i < mUserDataArray.size(); i++) { - UserData userData = mUserDataArray.get(i); + int userId = mUserDataArray.keyAt(i); + UserData userData = mUserDataArray.get(userId); if (userData.isUnlocked()) { consumer.accept(userData); } @@ -385,36 +383,6 @@ public class DataManager { } @VisibleForTesting - @WorkerThread - void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) { - UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( - userId, lastQueryTime, currentTime, false, false); - if (usageEvents == null) { - return; - } - while (usageEvents.hasNextEvent()) { - UsageEvents.Event e = new UsageEvents.Event(); - usageEvents.getNextEvent(e); - - String packageName = e.getPackageName(); - PackageData packageData = getPackage(packageName, userId); - if (packageData == null) { - continue; - } - if (e.getEventType() == UsageEvents.Event.SHORTCUT_INVOCATION) { - String shortcutId = e.getShortcutId(); - if (packageData.getConversationStore().getConversation(shortcutId) != null) { - EventHistoryImpl eventHistory = - packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutId); - eventHistory.addEvent( - new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); - } - } - } - } - - @VisibleForTesting ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) { return mContactsContentObservers.get(userId); } @@ -611,19 +579,20 @@ public class DataManager { */ private class UsageStatsQueryRunnable implements Runnable { - private final int mUserId; - private long mLastQueryTime; + private final UsageStatsQueryHelper mUsageStatsQueryHelper; + private long mLastEventTimestamp; private UsageStatsQueryRunnable(int userId) { - mUserId = userId; - mLastQueryTime = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; + mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId, + (packageName) -> getPackage(packageName, userId)); + mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; } @Override public void run() { - long currentTime = System.currentTimeMillis(); - queryUsageStatsService(mUserId, currentTime, mLastQueryTime); - mLastQueryTime = currentTime; + if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) { + mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp(); + } } } @@ -679,6 +648,11 @@ public class DataManager { return new SmsQueryHelper(context, eventConsumer); } + UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId, + Function<String, PackageData> packageDataGetter) { + return new UsageStatsQueryHelper(userId, packageDataGetter); + } + int getCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } diff --git a/services/people/java/com/android/server/people/data/Event.java b/services/people/java/com/android/server/people/data/Event.java index c2364a295e30..81411c00db51 100644 --- a/services/people/java/com/android/server/people/data/Event.java +++ b/services/people/java/com/android/server/people/data/Event.java @@ -18,14 +18,12 @@ package com.android.server.people.data; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.text.format.DateFormat; import android.util.ArraySet; -import com.android.internal.util.Preconditions; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.Set; /** An event representing the interaction with a specific conversation or app. */ @@ -55,6 +53,8 @@ public class Event { public static final int TYPE_CALL_MISSED = 12; + public static final int TYPE_IN_APP_CONVERSATION = 13; + @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHORTCUT_INVOCATION, TYPE_NOTIFICATION_POSTED, @@ -68,6 +68,7 @@ public class Event { TYPE_CALL_OUTGOING, TYPE_CALL_INCOMING, TYPE_CALL_MISSED, + TYPE_IN_APP_CONVERSATION, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} @@ -95,6 +96,7 @@ public class Event { CALL_EVENT_TYPES.add(TYPE_CALL_MISSED); ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION); + ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION); ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES); @@ -105,18 +107,18 @@ public class Event { private final int mType; - private final CallDetails mCallDetails; + private final int mDurationSeconds; Event(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; - mCallDetails = null; + mDurationSeconds = 0; } private Event(@NonNull Builder builder) { mTimestamp = builder.mTimestamp; mType = builder.mType; - mCallDetails = builder.mCallDetails; + mDurationSeconds = builder.mDurationSeconds; } public long getTimestamp() { @@ -128,12 +130,35 @@ public class Event { } /** - * Gets the {@link CallDetails} of the event. It is only available if the event type is one of - * {@code CALL_EVENT_TYPES}, otherwise, it's always {@code null}. + * Gets the duration of the event in seconds. It is only available for these events: + * <ul> + * <li>{@link #TYPE_CALL_INCOMING} + * <li>{@link #TYPE_CALL_OUTGOING} + * <li>{@link #TYPE_IN_APP_CONVERSATION} + * </ul> + * <p>For the other event types, it always returns {@code 0}. */ - @Nullable - public CallDetails getCallDetails() { - return mCallDetails; + public int getDurationSeconds() { + return mDurationSeconds; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Event)) { + return false; + } + Event other = (Event) obj; + return mTimestamp == other.mTimestamp + && mType == other.mType + && mDurationSeconds == other.mDurationSeconds; + } + + @Override + public int hashCode() { + return Objects.hash(mTimestamp, mType, mDurationSeconds); } @Override @@ -142,32 +167,13 @@ public class Event { sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); - if (mCallDetails != null) { - sb.append(", callDetails=").append(mCallDetails); + if (mDurationSeconds > 0) { + sb.append(", durationSeconds=").append(mDurationSeconds); } sb.append("}"); return sb.toString(); } - /** Type-specific details of a call event. */ - public static class CallDetails { - - private final long mDurationSeconds; - - CallDetails(long durationSeconds) { - mDurationSeconds = durationSeconds; - } - - public long getDurationSeconds() { - return mDurationSeconds; - } - - @Override - public String toString() { - return "CallDetails {durationSeconds=" + mDurationSeconds + "}"; - } - } - /** Builder class for {@link Event} objects. */ static class Builder { @@ -175,16 +181,15 @@ public class Event { private final int mType; - private CallDetails mCallDetails; + private int mDurationSeconds; Builder(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; } - Builder setCallDetails(CallDetails callDetails) { - Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType)); - mCallDetails = callDetails; + Builder setDurationSeconds(int durationSeconds) { + mDurationSeconds = durationSeconds; return this; } diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java new file mode 100644 index 000000000000..4e37f47149b4 --- /dev/null +++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java @@ -0,0 +1,158 @@ +/* + * 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.NonNull; +import android.annotation.UserIdInt; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.LocusId; +import android.text.format.DateUtils; +import android.util.ArrayMap; + +import com.android.server.LocalServices; + +import java.util.Map; +import java.util.function.Function; + +/** A helper class that queries {@link UsageStatsManagerInternal}. */ +class UsageStatsQueryHelper { + + private final UsageStatsManagerInternal mUsageStatsManagerInternal; + private final int mUserId; + private final Function<String, PackageData> mPackageDataGetter; + // Activity name -> Conversation start event (LOCUS_ID_SET) + private final Map<ComponentName, UsageEvents.Event> mConvoStartEvents = new ArrayMap<>(); + private long mLastEventTimestamp; + + /** + * @param userId The user whose events are to be queried. + * @param packageDataGetter The function to get {@link PackageData} with a package name. + */ + UsageStatsQueryHelper(@UserIdInt int userId, + Function<String, PackageData> packageDataGetter) { + mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mUserId = userId; + mPackageDataGetter = packageDataGetter; + } + + /** + * Queries {@link UsageStatsManagerInternal} for the recent events occurred since {@code + * sinceTime} and adds the derived {@link Event}s into the corresponding package's event store, + * + * @return true if the query runs successfully and at least one event is found. + */ + boolean querySince(long sinceTime) { + UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( + mUserId, sinceTime, System.currentTimeMillis(), false, false); + if (usageEvents == null) { + return false; + } + boolean hasEvents = false; + while (usageEvents.hasNextEvent()) { + UsageEvents.Event e = new UsageEvents.Event(); + usageEvents.getNextEvent(e); + + hasEvents = true; + mLastEventTimestamp = Math.max(mLastEventTimestamp, e.getTimeStamp()); + String packageName = e.getPackageName(); + PackageData packageData = mPackageDataGetter.apply(packageName); + if (packageData == null) { + continue; + } + switch (e.getEventType()) { + case UsageEvents.Event.SHORTCUT_INVOCATION: + addEventByShortcutId(packageData, e.getShortcutId(), + new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + addEventByNotificationChannelId(packageData, e.getNotificationChannelId(), + new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED)); + break; + case UsageEvents.Event.LOCUS_ID_SET: + onInAppConversationEnded(packageData, e); + LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null; + if (locusId != null) { + if (packageData.getConversationStore().getConversationByLocusId(locusId) + != null) { + ComponentName activityName = + new ComponentName(packageName, e.getClassName()); + mConvoStartEvents.put(activityName, e); + } + } + break; + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + onInAppConversationEnded(packageData, e); + break; + } + } + return hasEvents; + } + + long getLastEventTimestamp() { + return mLastEventTimestamp; + } + + private void onInAppConversationEnded(@NonNull PackageData packageData, + @NonNull UsageEvents.Event endEvent) { + ComponentName activityName = + new ComponentName(endEvent.getPackageName(), endEvent.getClassName()); + UsageEvents.Event startEvent = mConvoStartEvents.remove(activityName); + if (startEvent == null || startEvent.getTimeStamp() >= endEvent.getTimeStamp()) { + return; + } + long durationMillis = endEvent.getTimeStamp() - startEvent.getTimeStamp(); + Event event = new Event.Builder(startEvent.getTimeStamp(), Event.TYPE_IN_APP_CONVERSATION) + .setDurationSeconds((int) (durationMillis / DateUtils.SECOND_IN_MILLIS)) + .build(); + addEventByLocusId(packageData, new LocusId(startEvent.getLocusId()), event); + } + + private void addEventByShortcutId(PackageData packageData, String shortcutId, Event event) { + if (packageData.getConversationStore().getConversation(shortcutId) == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( + shortcutId); + eventHistory.addEvent(event); + } + + private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) { + if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( + locusId); + eventHistory.addEvent(event); + } + + private void addEventByNotificationChannelId(PackageData packageData, + String notificationChannelId, Event event) { + ConversationInfo conversationInfo = + packageData.getConversationStore().getConversationByNotificationChannelId( + notificationChannelId); + if (conversationInfo == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( + conversationInfo.getShortcutId()); + eventHistory.addEvent(event); + } +} diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 3d9f11ff6d2f..339ff6b8b526 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -22,6 +22,7 @@ android_test { "services.net", "service-jobscheduler", "service-permission", + "service-blobstore", "androidx.test.runner", "mockito-target-extended-minus-junit4", "platform-test-annotations", diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 0e24b0314b2d..44eb8285c7db 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -17,6 +17,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.mockingservicestests"> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> diff --git a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml index a37d84f7a08c..a37d84f7a08c 100644 --- a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml +++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 155de3bc0266..2d5fa237f6b7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -101,9 +101,8 @@ public class AppOpsServiceTest { private StaticMockitoSession mMockingSession; private void setupAppOpsService() { - mAppOpsService = new AppOpsService(mAppOpsFile, mHandler); + mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext)); mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver()); - mAppOpsService.mContext = spy(sContext); // Always approve all permission checks doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(), diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index 66d2baba2909..e48b67167cdf 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -19,9 +19,17 @@ package com.android.server.appop; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.os.Handler; import android.os.HandlerThread; @@ -133,10 +141,24 @@ public class AppOpsUpgradeTest { AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile); assertTrue(parser.parse()); assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion); - AppOpsService testService = new AppOpsService(mAppOpsFile, mHandler); // trigger upgrade + + // Use mock context and package manager to fake permision package manager calls. + Context testContext = spy(mContext); + + // Pretent everybody has all permissions + doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(), + nullable(String.class)); + + PackageManager testPM = mock(PackageManager.class); + when(testContext.getPackageManager()).thenReturn(testPM); + + // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat + when(testPM.getPackagesForUid(anyInt())).thenReturn(null); + + AppOpsService testService = spy( + new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); - testService.mContext = mContext; mHandler.removeCallbacks(testService.mWriteRunner); testService.writeState(); assertTrue(parser.parse()); diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java new file mode 100644 index 000000000000..16dde4203e91 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -0,0 +1,342 @@ +/* + * 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.blob; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.blob.BlobHandle; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.util.LongSparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.blob.BlobStoreManagerService.Injector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class BlobStoreManagerServiceTest { + private Context mContext; + private Handler mHandler; + private BlobStoreManagerService mService; + + private MockitoSession mMockitoSession; + + @Mock + private File mBlobsDir; + + private LongSparseArray<BlobStoreSession> mUserSessions; + private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; + + private static final String TEST_PKG1 = "com.example1"; + private static final String TEST_PKG2 = "com.example2"; + private static final String TEST_PKG3 = "com.example3"; + + private static final int TEST_UID1 = 10001; + private static final int TEST_UID2 = 10002; + private static final int TEST_UID3 = 10003; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + + mMockitoSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(BlobStoreConfig.class) + .startMocking(); + + doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir()); + doReturn(true).when(mBlobsDir).exists(); + doReturn(new File[0]).when(mBlobsDir).listFiles(); + + mContext = InstrumentationRegistry.getTargetContext(); + mHandler = new TestHandler(Looper.getMainLooper()); + mService = new BlobStoreManagerService(mContext, new TestInjector()); + mUserSessions = new LongSparseArray<>(); + mUserBlobs = new ArrayMap<>(); + + mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); + mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); + } + + @After + public void tearDown() { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + } + + @Test + public void testHandlePackageRemoved() throws Exception { + // Setup sessions + final File sessionFile1 = mock(File.class); + final long sessionId1 = 11; + final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId1, sessionFile1); + mUserSessions.append(sessionId1, session1); + + final File sessionFile2 = mock(File.class); + final long sessionId2 = 25; + final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, + sessionId2, sessionFile2); + mUserSessions.append(sessionId2, session2); + + final File sessionFile3 = mock(File.class); + final long sessionId3 = 37; + final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, + sessionId3, sessionFile3); + mUserSessions.append(sessionId3, session3); + + final File sessionFile4 = mock(File.class); + final long sessionId4 = 48; + final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId4, sessionFile4); + mUserSessions.append(sessionId4, session4); + + // Setup blobs + final long blobId1 = 978; + final File blobFile1 = mock(File.class); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis(), "tag1"); + final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true); + mUserBlobs.put(blobHandle1, blobMetadata1); + + final long blobId2 = 347; + final File blobFile2 = mock(File.class); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis(), "tag2"); + final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, false); + mUserBlobs.put(blobHandle2, blobMetadata2); + + mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4, + blobId1, blobId2); + + // Invoke test method + mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); + + // Verify sessions are removed + verify(sessionFile1).delete(); + verify(sessionFile2, never()).delete(); + verify(sessionFile3, never()).delete(); + verify(sessionFile4).delete(); + + assertThat(mUserSessions.size()).isEqualTo(2); + assertThat(mUserSessions.get(sessionId1)).isNull(); + assertThat(mUserSessions.get(sessionId2)).isNotNull(); + assertThat(mUserSessions.get(sessionId3)).isNotNull(); + assertThat(mUserSessions.get(sessionId4)).isNull(); + + // Verify blobs are removed + verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); + + verify(blobFile1, never()).delete(); + verify(blobFile2).delete(); + + assertThat(mUserBlobs.size()).isEqualTo(1); + assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); + assertThat(mUserBlobs.get(blobHandle2)).isNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly( + sessionId2, sessionId3, blobId1); + } + + @Test + public void testHandleIdleMaintenance_deleteUnknownBlobs() throws Exception { + // Setup blob files + final long testId1 = 286; + final File file1 = mock(File.class); + doReturn(String.valueOf(testId1)).when(file1).getName(); + final long testId2 = 349; + final File file2 = mock(File.class); + doReturn(String.valueOf(testId2)).when(file2).getName(); + final long testId3 = 7355; + final File file3 = mock(File.class); + doReturn(String.valueOf(testId3)).when(file3).getName(); + + doReturn(new File[] {file1, file2, file3}).when(mBlobsDir).listFiles(); + mService.addKnownIdsForTest(testId1, testId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify unknown blobs are delete + verify(file1, never()).delete(); + verify(file2).delete(); + verify(file3, never()).delete(); + } + + @Test + public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { + // Setup sessions + final File sessionFile1 = mock(File.class); + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000) + .when(sessionFile1).lastModified(); + final long sessionId1 = 342; + final BlobHandle blobHandle1 = mock(BlobHandle.class); + doReturn(System.currentTimeMillis() - 1000).when(blobHandle1).getExpiryTimeMillis(); + final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId1, sessionFile1, blobHandle1); + mUserSessions.append(sessionId1, session1); + + final File sessionFile2 = mock(File.class); + doReturn(System.currentTimeMillis() - 20000) + .when(sessionFile2).lastModified(); + final long sessionId2 = 4597; + final BlobHandle blobHandle2 = mock(BlobHandle.class); + doReturn(System.currentTimeMillis() + 20000).when(blobHandle2).getExpiryTimeMillis(); + final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, + sessionId2, sessionFile2, blobHandle2); + mUserSessions.append(sessionId2, session2); + + final File sessionFile3 = mock(File.class); + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000) + .when(sessionFile3).lastModified(); + final long sessionId3 = 9484; + final BlobHandle blobHandle3 = mock(BlobHandle.class); + doReturn(System.currentTimeMillis() + 30000).when(blobHandle3).getExpiryTimeMillis(); + final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, + sessionId3, sessionFile3, blobHandle3); + mUserSessions.append(sessionId3, session3); + + mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify stale sessions are removed + verify(sessionFile1).delete(); + verify(sessionFile2, never()).delete(); + verify(sessionFile3).delete(); + + assertThat(mUserSessions.size()).isEqualTo(1); + assertThat(mUserSessions.get(sessionId2)).isNotNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly(sessionId2); + } + + @Test + public void testHandleIdleMaintenance_deleteStaleBlobs() throws Exception { + // Setup blobs + final long blobId1 = 3489; + final File blobFile1 = mock(File.class); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis() - 2000, "tag1"); + final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true); + mUserBlobs.put(blobHandle1, blobMetadata1); + + final long blobId2 = 78974; + final File blobFile2 = mock(File.class); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis() + 30000, "tag2"); + final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, true); + mUserBlobs.put(blobHandle2, blobMetadata2); + + final long blobId3 = 97; + final File blobFile3 = mock(File.class); + final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), + "label3", System.currentTimeMillis() + 4400000, "tag3"); + final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, false); + mUserBlobs.put(blobHandle3, blobMetadata3); + + mService.addKnownIdsForTest(blobId1, blobId2, blobId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify stale blobs are removed + verify(blobFile1).delete(); + verify(blobFile2, never()).delete(); + verify(blobFile3).delete(); + + assertThat(mUserBlobs.size()).isEqualTo(1); + assertThat(mUserBlobs.get(blobHandle2)).isNotNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly(blobId2); + } + + private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, + long sessionId, File sessionFile) { + return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile, + mock(BlobHandle.class)); + } + private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, + long sessionId, File sessionFile, BlobHandle blobHandle) { + final BlobStoreSession session = mock(BlobStoreSession.class); + doReturn(ownerPackageName).when(session).getOwnerPackageName(); + doReturn(ownerUid).when(session).getOwnerUid(); + doReturn(sessionId).when(session).getSessionId(); + doReturn(sessionFile).when(session).getSessionFile(); + doReturn(blobHandle).when(session).getBlobHandle(); + return session; + } + + private BlobMetadata createBlobMetadataMock(long blobId, File blobFile, boolean hasLeases) { + final BlobMetadata blobMetadata = mock(BlobMetadata.class); + doReturn(blobId).when(blobMetadata).getBlobId(); + doReturn(blobFile).when(blobMetadata).getBlobFile(); + doReturn(hasLeases).when(blobMetadata).hasLeases(); + return blobMetadata; + } + + private class TestHandler extends Handler { + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void dispatchMessage(Message msg) { + // Ignore all messages + } + } + + private class TestInjector extends Injector { + @Override + public Handler initializeMessageHandler() { + return mHandler; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 4a40b80a85c1..6d1530219372 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -20,6 +20,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -29,6 +31,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; @@ -47,6 +50,7 @@ import org.mockito.Mock; import org.mockito.quality.Strictness; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; @@ -167,6 +171,7 @@ public class LocalDisplayAdapterTest { */ @Test public void testDpiValues() throws Exception { + // needs default one always setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); updateAvailableDisplays(); @@ -182,6 +187,67 @@ public class LocalDisplayAdapterTest { 16000); } + @Test + public void testAfterDisplayChange_ModesAreUpdated() throws Exception { + SurfaceControl.DisplayConfig displayInfo = createFakeDisplayConfig(1920, 1080, 60f); + SurfaceControl.DisplayConfig[] configs = + new SurfaceControl.DisplayConfig[]{displayInfo}; + FakeDisplay display = new FakeDisplay(PORT_A, configs, 0); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays).isEmpty(); + + DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( + 0).getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo); + + Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(defaultMode.matches(displayInfo.width, displayInfo.height, + displayInfo.refreshRate)).isTrue(); + + Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); + assertThat(activeMode.matches(displayInfo.width, displayInfo.height, + displayInfo.refreshRate)).isTrue(); + + // Change the display + SurfaceControl.DisplayConfig addedDisplayInfo = createFakeDisplayConfig(3840, 2160, + 60f); + configs = new SurfaceControl.DisplayConfig[]{displayInfo, addedDisplayInfo}; + display.configs = configs; + display.activeConfig = 1; + setUpDisplay(display); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(SurfaceControl.getActiveConfig(display.token)).isEqualTo(1); + assertThat(SurfaceControl.getDisplayConfigs(display.token).length).isEqualTo(2); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + + DisplayDevice displayDevice = mListener.changedDisplays.get(0); + displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); + displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo); + assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); + + activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); + assertThat(activeMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, + addedDisplayInfo.refreshRate)).isTrue(); + + defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(defaultMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, + addedDisplayInfo.refreshRate)).isTrue(); + } + private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort, float expectedXdpi, float expectedYDpi, @@ -194,16 +260,40 @@ public class LocalDisplayAdapterTest { assertEquals(expectedDensityDpi, info.densityDpi); } + private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) { + return Arrays.stream(displayDeviceInfo.supportedModes) + .filter(mode -> mode.getModeId() == modeId) + .findFirst() + .get(); + } + + private void assertModeIsSupported(Display.Mode[] supportedModes, + SurfaceControl.DisplayConfig mode) { + assertThat(Arrays.stream(supportedModes).anyMatch( + x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue(); + } + private static class FakeDisplay { public final DisplayAddress.Physical address; public final IBinder token = new Binder(); public final SurfaceControl.DisplayInfo info; - public final SurfaceControl.DisplayConfig config; + public SurfaceControl.DisplayConfig[] configs; + public int activeConfig; private FakeDisplay(int port) { this.address = createDisplayAddress(port); this.info = createFakeDisplayInfo(); - this.config = createFakeDisplayConfig(); + this.configs = new SurfaceControl.DisplayConfig[]{ + createFakeDisplayConfig(800, 600, 60f) + }; + this.activeConfig = 0; + } + + private FakeDisplay(int port, SurfaceControl.DisplayConfig[] configs, int activeConfig) { + this.address = createDisplayAddress(port); + this.info = createFakeDisplayInfo(); + this.configs = configs; + this.activeConfig = activeConfig; } } @@ -212,9 +302,9 @@ public class LocalDisplayAdapterTest { doReturn(display.token).when(() -> SurfaceControl.getPhysicalDisplayToken(display.address.getPhysicalDisplayId())); doReturn(display.info).when(() -> SurfaceControl.getDisplayInfo(display.token)); - doReturn(new SurfaceControl.DisplayConfig[] { display.config }).when( + doReturn(display.configs).when( () -> SurfaceControl.getDisplayConfigs(display.token)); - doReturn(0).when(() -> SurfaceControl.getActiveConfig(display.token)); + doReturn(display.activeConfig).when(() -> SurfaceControl.getActiveConfig(display.token)); doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token)); doReturn(new int[] { 0 }).when( () -> SurfaceControl.getDisplayColorModes(display.token)); @@ -242,10 +332,12 @@ public class LocalDisplayAdapterTest { return info; } - private static SurfaceControl.DisplayConfig createFakeDisplayConfig() { + private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height, + float refreshRate) { final SurfaceControl.DisplayConfig config = new SurfaceControl.DisplayConfig(); - config.width = 800; - config.height = 600; + config.width = width; + config.height = height; + config.refreshRate = refreshRate; config.xDpi = 100; config.yDpi = 100; return config; @@ -266,17 +358,19 @@ public class LocalDisplayAdapterTest { private class TestListener implements DisplayAdapter.Listener { public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>(); + public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>(); @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) { addedDisplays.add(device); + } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) { + changedDisplays.add(device); } } @Override public void onTraversalRequested() { - } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java index 8e9d7ee71df3..3566aee2eba3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import org.junit.AssumptionViolatedException; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -100,6 +101,11 @@ public class StaticMockFixtureRule implements TestRule { } @Override + protected void skipped(AssumptionViolatedException e, Description description) { + tearDown(e); + } + + @Override protected void failed(Throwable e, Description description) { tearDown(e); } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java index b7e71ded30ab..8e0ccf01d7a7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java @@ -16,8 +16,10 @@ package com.android.server.testables; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -30,6 +32,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import org.junit.After; +import org.junit.AssumptionViolatedException; import org.junit.Before; import org.junit.Test; import org.junit.runner.Description; @@ -57,6 +60,8 @@ public class StaticMockFixtureRuleTest { @Mock private Supplier<StaticMockFixture> mSupplyA; @Mock private Supplier<StaticMockFixture> mSupplyB; @Mock private Statement mStatement; + @Mock private Statement mSkipStatement; + @Mock private Statement mThrowStatement; @Mock private Description mDescription; @Before @@ -91,17 +96,22 @@ public class StaticMockFixtureRuleTest { when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0)); doNothing().when(mB1).setUpMockBehaviors(); doNothing().when(mStatement).evaluate(); + doThrow(new AssumptionViolatedException("bad assumption, test should be skipped")) + .when(mSkipStatement).evaluate(); + doThrow(new IllegalArgumentException("bad argument, test should be failed")) + .when(mThrowStatement).evaluate(); doNothing().when(mA1).tearDown(); doNothing().when(mB1).tearDown(); } private InOrder mocksInOrder() { - return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, - mA1, mA2, mB1, mB2, mStatement, mDescription); + return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, + mStatement, mSkipStatement, mThrowStatement, mDescription); } private void verifyNoMoreImportantMockInteractions() { - verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement); + verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement, + mSkipStatement, mThrowStatement); } @Test @@ -183,4 +193,66 @@ public class StaticMockFixtureRuleTest { verifyNoMoreImportantMockInteractions(); } + + @Test + public void testTearDownOnSkippedTests() throws Throwable { + InOrder inOrder = mocksInOrder(); + + StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { + @Override public StaticMockitoSessionBuilder getSessionBuilder() { + return mSessionBuilder; + } + }; + Statement skipStatement = rule.apply(mSkipStatement, mDescription); + + inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mA1).setUpMockBehaviors(); + inOrder.verify(mB1).setUpMockBehaviors(); + + try { + skipStatement.evaluate(); + fail("AssumptionViolatedException should have been thrown"); + } catch (AssumptionViolatedException e) { + // expected + } + + inOrder.verify(mSkipStatement).evaluate(); + // note: tearDown in reverse order + inOrder.verify(mB1).tearDown(); + inOrder.verify(mA1).tearDown(); + + verifyNoMoreImportantMockInteractions(); + } + + @Test + public void testTearDownOnFailedTests() throws Throwable { + InOrder inOrder = mocksInOrder(); + + StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { + @Override public StaticMockitoSessionBuilder getSessionBuilder() { + return mSessionBuilder; + } + }; + Statement failStatement = rule.apply(mThrowStatement, mDescription); + + inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mA1).setUpMockBehaviors(); + inOrder.verify(mB1).setUpMockBehaviors(); + + try { + failStatement.evaluate(); + fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + inOrder.verify(mThrowStatement).evaluate(); + // note: tearDown in reverse order + inOrder.verify(mB1).tearDown(); + inOrder.verify(mA1).tearDown(); + + verifyNoMoreImportantMockInteractions(); + } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 8381205fa48e..f99081024494 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -46,7 +46,6 @@ android_test { "service-appsearch", "service-jobscheduler", "service-permission", - "service-blobstore", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index d2ddff3627b9..1212f2082404 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -65,6 +65,8 @@ <uses-permission android:name="android.permission.WATCH_APPOPS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.SUSPEND_APPS"/> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/> <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index cf10559c8198..dfe950ea93d6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -47,6 +47,7 @@ import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -703,14 +704,20 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void takeScreenshot_returnNull() { - // no canTakeScreenshot, should return null. - when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); - // no checkAccessibilityAccess, should return null. when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { + assertNull(result); + })); + } + + @Test (expected = SecurityException.class) + public void takeScreenshot_throwSecurityException() { + // no canTakeScreenshot, should throw security exception. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { + })); } private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, @@ -852,8 +859,5 @@ public class AbstractAccessibilityServiceConnectionTest { @Override public void onFingerprintGesture(int gesture) {} - - @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index d38c80cdabe0..6aa928794244 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +33,9 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.face.IFaceService; +import android.hardware.fingerprint.IFingerprintService; +import android.hardware.iris.IIrisService; import android.os.Binder; import android.os.Bundle; @@ -61,6 +65,12 @@ public class AuthServiceTest { AuthService.Injector mInjector; @Mock IBiometricService mBiometricService; + @Mock + IFingerprintService mFingerprintService; + @Mock + IIrisService mIrisService; + @Mock + IFaceService mFaceService; @Before public void setUp() { @@ -76,10 +86,28 @@ public class AuthServiceTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mInjector.getBiometricService()).thenReturn(mBiometricService); when(mInjector.getConfiguration(any())).thenReturn(config); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) - .thenReturn(true); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mInjector.getFingerprintService()).thenReturn(mFingerprintService); + when(mInjector.getFaceService()).thenReturn(mFaceService); + when(mInjector.getIrisService()).thenReturn(mIrisService); + } + + @Test + public void testRegisterNullService_doesNotRegister() throws Exception { + + // Config contains Fingerprint, Iris, Face, but services are all null + + when(mInjector.getFingerprintService()).thenReturn(null); + when(mInjector.getFaceService()).thenReturn(null); + when(mInjector.getIrisService()).thenReturn(null); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + verify(mBiometricService, never()).registerAuthenticator( + anyInt(), + anyInt(), + anyInt(), + any()); } diff --git a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java deleted file mode 100644 index ff728e7a4017..000000000000 --- a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.blob; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.blob.BlobHandle; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.UserHandle; -import android.platform.test.annotations.Presubmit; -import android.util.ArrayMap; -import android.util.LongSparseArray; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.blob.BlobStoreManagerService.Injector; -import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class BlobStoreManagerServiceTest { - private Context mContext; - private Handler mHandler; - private BlobStoreManagerService mService; - - private LongSparseArray<BlobStoreSession> mUserSessions; - private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; - - private SessionStateChangeListener mStateChangeListener; - - private static final String TEST_PKG1 = "com.example1"; - private static final String TEST_PKG2 = "com.example2"; - private static final String TEST_PKG3 = "com.example3"; - - private static final int TEST_UID1 = 10001; - private static final int TEST_UID2 = 10002; - private static final int TEST_UID3 = 10003; - - @Before - public void setUp() { - // Share classloader to allow package private access. - System.setProperty("dexmaker.share_classloader", "true"); - - mContext = InstrumentationRegistry.getTargetContext(); - mHandler = new TestHandler(Looper.getMainLooper()); - mService = new BlobStoreManagerService(mContext, new TestInjector()); - mUserSessions = new LongSparseArray<>(); - mUserBlobs = new ArrayMap<>(); - - mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); - mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); - - mStateChangeListener = mService.new SessionStateChangeListener(); - } - - @Test - public void testHandlePackageRemoved() throws Exception { - // Setup sessions - final File sessionFile1 = mock(File.class); - final long sessionId1 = 11; - final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, - sessionId1, sessionFile1); - mUserSessions.append(sessionId1, session1); - - final File sessionFile2 = mock(File.class); - final long sessionId2 = 25; - final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, - sessionId2, sessionFile2); - mUserSessions.append(sessionId2, session2); - - final File sessionFile3 = mock(File.class); - final long sessionId3 = 37; - final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, - sessionId3, sessionFile3); - mUserSessions.append(sessionId3, session3); - - final File sessionFile4 = mock(File.class); - final long sessionId4 = 48; - final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, - sessionId4, sessionFile4); - mUserSessions.append(sessionId4, session4); - - // Setup blobs - final File blobFile1 = mock(File.class); - final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), - "label1", System.currentTimeMillis(), "tag1"); - final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobFile1, true); - mUserBlobs.put(blobHandle1, blobMetadata1); - - final File blobFile2 = mock(File.class); - final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), - "label2", System.currentTimeMillis(), "tag2"); - final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobFile2, false); - mUserBlobs.put(blobHandle2, blobMetadata2); - - // Invoke test method - mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); - - // Verify sessions are removed - verify(sessionFile1).delete(); - verify(sessionFile2, never()).delete(); - verify(sessionFile3, never()).delete(); - verify(sessionFile4).delete(); - - assertThat(mUserSessions.size()).isEqualTo(2); - assertThat(mUserSessions.get(sessionId1)).isNull(); - assertThat(mUserSessions.get(sessionId2)).isNotNull(); - assertThat(mUserSessions.get(sessionId3)).isNotNull(); - assertThat(mUserSessions.get(sessionId4)).isNull(); - - // Verify blobs are removed - verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); - verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); - verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1); - verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); - - verify(blobFile1, never()).delete(); - verify(blobFile2).delete(); - - assertThat(mUserBlobs.size()).isEqualTo(1); - assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); - assertThat(mUserBlobs.get(blobHandle2)).isNull(); - } - - private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, - long sessionId, File sessionFile) { - final BlobStoreSession session = mock(BlobStoreSession.class); - when(session.getOwnerPackageName()).thenReturn(ownerPackageName); - when(session.getOwnerUid()).thenReturn(ownerUid); - when(session.getSessionId()).thenReturn(sessionId); - when(session.getSessionFile()).thenReturn(sessionFile); - return session; - } - - private BlobMetadata createBlobMetadataMock(File blobFile, boolean hasLeases) { - final BlobMetadata blobMetadata = mock(BlobMetadata.class); - when(blobMetadata.getBlobFile()).thenReturn(blobFile); - when(blobMetadata.hasLeases()).thenReturn(hasLeases); - return blobMetadata; - } - - private class TestHandler extends Handler { - TestHandler(Looper looper) { - super(looper); - } - - @Override - public void dispatchMessage(Message msg) { - // Ignore all messages - } - } - - private class TestInjector extends Injector { - @Override - public Handler initializeMessageHandler() { - return mHandler; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index b193a34952f4..39a749f9a6dc 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -96,6 +96,7 @@ import android.test.MoreAsserts; import android.util.ArraySet; import android.util.Pair; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -4535,6 +4536,86 @@ public class DevicePolicyManagerTest extends DpmTestBase { .removeUserEvenWhenDisallowed(anyInt()); } + public void testMaximumFailedDevicePasswordAttemptsReachedOrgOwnedManagedProfile() + throws Exception { + final int MANAGED_PROFILE_USER_ID = 15; + final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + + // Even if the caller is the managed profile, the current user is the user 0 + when(getServices().iactivityManager.getCurrentUser()) + .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); + + configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + dpm.setMaximumFailedPasswordsForWipe(admin1, 3); + + assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(admin1)); + assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null)); + + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); + + assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); + // Check that primary will be wiped as a result of failed primary user unlock attempts. + assertEquals(UserHandle.USER_SYSTEM, + dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); + + // Failed password attempts on the parent user are taken into account, as there isn't a + // separate work challenge. + dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); + dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); + dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); + + // For managed profile on an organization owned device, the whole device should be wiped. + verify(getServices().recoverySystem).rebootWipeUserData( + /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), + /*wipeEuicc=*/ eq(false)); + } + + public void testMaximumFailedProfilePasswordAttemptsReachedOrgOwnedManagedProfile() + throws Exception { + final int MANAGED_PROFILE_USER_ID = 15; + final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + + // Even if the caller is the managed profile, the current user is the user 0 + when(getServices().iactivityManager.getCurrentUser()) + .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); + + doReturn(true).when(getServices().lockPatternUtils) + .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); + + // Configure separate challenge. + configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + dpm.setMaximumFailedPasswordsForWipe(admin1, 3); + + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); + + assertEquals(0, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM)); + assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, MANAGED_PROFILE_USER_ID)); + // Check that the policy is not affecting primary profile challenge. + assertEquals(UserHandle.USER_NULL, + dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM)); + // Check that primary will be wiped as a result of failed profile unlock attempts. + assertEquals(UserHandle.USER_SYSTEM, + dpm.getProfileWithMinimumFailedPasswordsForWipe(MANAGED_PROFILE_USER_ID)); + + // Simulate three failed attempts at solving the separate challenge. + dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); + dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); + dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID); + + // For managed profile on an organization owned device, the whole device should be wiped. + verify(getServices().recoverySystem).rebootWipeUserData( + /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true), + /*wipeEuicc=*/ eq(false)); + } + public void testGetPermissionGrantState() throws Exception { final String permission = "some.permission"; final String app1 = "com.example.app1"; @@ -5366,26 +5447,28 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM)); } - public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception { - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getDeviceOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated), - getDeviceOwnerFile()); - assertDeviceOwnershipRevertedWithFakeTransferMetadata(); - } - - public void testRevertDeviceOwnership_deviceNotMigrated() - throws Exception { - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getDeviceOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), - getDeviceOwnerFile()); - assertDeviceOwnershipRevertedWithFakeTransferMetadata(); - } + // @FlakyTest(bugId = 148934649) + // public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception { + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getDeviceOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated), + // getDeviceOwnerFile()); + // assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + // } + + // @FlakyTest(bugId = 148934649) + // public void testRevertDeviceOwnership_deviceNotMigrated() + // throws Exception { + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getDeviceOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), + // getDeviceOwnerFile()); + // assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + // } public void testRevertDeviceOwnership_adminAndDeviceNotMigrated() throws Exception { @@ -5407,29 +5490,31 @@ public class DevicePolicyManagerTest extends DpmTestBase { UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE); } - public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception { - getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, - UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getProfileOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated), - getProfileOwnerFile()); - assertProfileOwnershipRevertedWithFakeTransferMetadata(); - } - - public void testRevertProfileOwnership_profileNotMigrated() throws Exception { - getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, - UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getProfileOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), - getProfileOwnerFile()); - assertProfileOwnershipRevertedWithFakeTransferMetadata(); - } + // @FlakyTest(bugId = 148934649) + // public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception { + // getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, + // UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getProfileOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated), + // getProfileOwnerFile()); + // assertProfileOwnershipRevertedWithFakeTransferMetadata(); + // } + + // @FlakyTest(bugId = 148934649) + // public void testRevertProfileOwnership_profileNotMigrated() throws Exception { + // getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, + // UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getProfileOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), + // getProfileOwnerFile()); + // assertProfileOwnershipRevertedWithFakeTransferMetadata(); + // } public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception { getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, @@ -5770,8 +5855,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId)))) .thenReturn(UserHandle.SYSTEM); final long ident = mServiceContext.binder.clearCallingIdentity(); - mServiceContext.binder.callingUid = - UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); + mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID); configureContextForAccess(mServiceContext, true); runAsCaller(mServiceContext, dpms, dpm -> { 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 4a7636a179b1..c9ec87427722 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -423,7 +423,8 @@ public class AppIntegrityManagerServiceImplTest { PackageInfo packageInfo = mRealContext .getPackageManager() - .getPackageInfo(TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNATURES); + .getPackageInfo(TEST_FRAMEWORK_PACKAGE, + PackageManager.GET_SIGNING_CERTIFICATES); doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt()); doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt()); return makeVerificationIntent(INSTALLER); diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java index 7a16d171b475..a54501029712 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java @@ -92,7 +92,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_INCOMING, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(30L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(30L, events.get(0).getDurationSeconds()); } @Test @@ -108,7 +108,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_OUTGOING, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(40L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(40L, events.get(0).getDurationSeconds()); } @Test @@ -124,7 +124,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_MISSED, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(0L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(0L, events.get(0).getDurationSeconds()); } @Test @@ -145,7 +145,7 @@ public final class CallLogQueryHelperTest { assertEquals(100L, events.get(0).getTimestamp()); assertEquals(Event.TYPE_CALL_OUTGOING, events.get(1).getType()); assertEquals(110L, events.get(1).getTimestamp()); - assertEquals(40L, events.get(1).getCallDetails().getDurationSeconds()); + assertEquals(40L, events.get(1).getDurationSeconds()); } private class EventConsumer implements BiConsumer<String, Event> { 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 a40c6ab90197..bbcb54ef8d3e 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 @@ -37,6 +37,7 @@ import java.util.Set; public final class ConversationStoreTest { private static final String SHORTCUT_ID = "abc"; + private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; private static final LocusId LOCUS_ID = new LocusId("def"); private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; @@ -59,16 +60,19 @@ public final class ConversationStoreTest { @Test public void testUpdateConversation() { - ConversationInfo original = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo original = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, null); mConversationStore.addOrUpdate(original); assertEquals(LOCUS_ID, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + assertNull(mConversationStore.getConversation(SHORTCUT_ID).getNotificationChannelId()); LocusId newLocusId = new LocusId("ghi"); ConversationInfo update = buildConversationInfo( - SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER); + SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(update); - assertEquals(newLocusId, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + ConversationInfo updated = mConversationStore.getConversation(SHORTCUT_ID); + assertEquals(newLocusId, updated.getLocusId()); + assertEquals(NOTIFICATION_CHANNEL_ID, updated.getNotificationChannelId()); } @Test @@ -97,8 +101,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByLocusId() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByLocusId(LOCUS_ID); assertNotNull(out); @@ -110,8 +114,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByContactUri() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByContactUri(CONTACT_URI); assertNotNull(out); @@ -123,8 +127,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByPhoneNumber() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER); assertNotNull(out); @@ -134,17 +138,34 @@ public final class ConversationStoreTest { assertNull(mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER)); } + @Test + public void testGetConversationByNotificationChannelId() { + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + mConversationStore.addOrUpdate(in); + ConversationInfo out = mConversationStore.getConversationByNotificationChannelId( + NOTIFICATION_CHANNEL_ID); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull( + mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID)); + } + private static ConversationInfo buildConversationInfo(String shortcutId) { - return buildConversationInfo(shortcutId, null, null, null); + return buildConversationInfo(shortcutId, null, null, null, null); } private static ConversationInfo buildConversationInfo( - String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber) { + String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber, + String notificationChannelId) { return new ConversationInfo.Builder() .setShortcutId(shortcutId) .setLocusId(locusId) .setContactUri(contactUri) .setContactPhoneNumber(phoneNumber) + .setNotificationChannelId(notificationChannelId) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) .setVip(true) .setBubbled(true) diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 62ea425a54a7..ad5c57dd11bc 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -16,15 +16,12 @@ package com.android.server.people.data; -import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; - import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; 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 static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -40,7 +37,6 @@ import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; -import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; @@ -232,7 +228,7 @@ public final class DataManagerTest { mDataManager.getShortcut(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID); verify(mShortcutServiceInternal).getShortcuts(anyInt(), anyString(), anyLong(), eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)), - eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt()); + eq(null), eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt()); } @Test @@ -263,6 +259,7 @@ public final class DataManagerTest { @Test public void testContactsChanged() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -289,6 +286,7 @@ public final class DataManagerTest { @Test public void testNotificationListener() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -310,37 +308,9 @@ public final class DataManagerTest { } @Test - public void testQueryUsageStatsService() { - UsageEvents.Event e = new UsageEvents.Event(SHORTCUT_INVOCATION, - System.currentTimeMillis()); - e.mPackage = TEST_PKG_NAME; - e.mShortcutId = TEST_SHORTCUT_ID; - List<UsageEvents.Event> events = new ArrayList<>(); - events.add(e); - UsageEvents usageEvents = new UsageEvents(events, new String[]{}); - when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), - anyBoolean(), anyBoolean())).thenReturn(usageEvents); - - mDataManager.onUserUnlocked(USER_ID_PRIMARY); - - ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, - buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); - - mDataManager.queryUsageStatsService(USER_ID_PRIMARY, 0L, Long.MAX_VALUE); - - List<Range<Long>> activeShortcutInvocationTimeSlots = new ArrayList<>(); - mDataManager.forAllPackages(packageData -> - activeShortcutInvocationTimeSlots.addAll( - packageData.getEventHistory(TEST_SHORTCUT_ID) - .getEventIndex(Event.TYPE_SHORTCUT_INVOCATION) - .getActiveTimeSlots())); - assertEquals(1, activeShortcutInvocationTimeSlots.size()); - } - - @Test public void testCallLogContentObserver() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -368,6 +338,7 @@ public final class DataManagerTest { @Test public void testMmsSmsContentObserver() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); 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 new file mode 100644 index 000000000000..e4248a04878d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -0,0 +1,295 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.content.LocusId; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +@RunWith(JUnit4.class) +public final class UsageStatsQueryHelperTest { + + private static final int USER_ID_PRIMARY = 0; + private static final String PKG_NAME = "pkg"; + private static final String ACTIVITY_NAME = "TestActivity"; + private static final String SHORTCUT_ID = "abc"; + private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; + private static final LocusId LOCUS_ID_1 = new LocusId("locus_1"); + private static final LocusId LOCUS_ID_2 = new LocusId("locus_2"); + + @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; + + private TestPackageData mPackageData; + private UsageStatsQueryHelper mHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); + + mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false); + mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setLocusId(LOCUS_ID_1) + .build(); + + mHelper = new UsageStatsQueryHelper(USER_ID_PRIMARY, pkg -> mPackageData); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + } + + @Test + public void testQueryNoEvents() { + assertFalse(mHelper.querySince(50L)); + } + + @Test + public void testQueryShortcutInvocationEvent() { + addUsageEvents(createShortcutInvocationEvent(100L)); + + assertTrue(mHelper.querySince(50L)); + assertEquals(100L, mHelper.getLastEventTimestamp()); + Event expectedEvent = new Event(100L, Event.TYPE_SHORTCUT_INVOCATION); + List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(expectedEvent, events.get(0)); + } + + @Test + public void testQueryNotificationInterruptionEvent() { + addUsageEvents(createNotificationInterruptionEvent(100L)); + + assertTrue(mHelper.querySince(50L)); + assertEquals(100L, mHelper.getLastEventTimestamp()); + Event expectedEvent = new Event(100L, Event.TYPE_NOTIFICATION_POSTED); + List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(expectedEvent, events.get(0)); + } + + @Test + public void testInAppConversationSwitch() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId())); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testInAppConversationExplicitlyEnd() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, null)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testInAppConversationImplicitlyEnd() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createActivityStoppedEvent(110_000L)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testMultipleInAppConversations() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()), + createLocusIdSetEvent(130_000L, LOCUS_ID_1.getId()), + createActivityStoppedEvent(160_000L)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(160_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(3, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + assertEquals(createInAppConversationEvent(110_000L, 20), events.get(1)); + assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2)); + } + + 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); + } + + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private static UsageEvents.Event createShortcutInvocationEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.SHORTCUT_INVOCATION, timestamp); + e.mShortcutId = SHORTCUT_ID; + return e; + } + + private static UsageEvents.Event createNotificationInterruptionEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.NOTIFICATION_INTERRUPTION, + timestamp); + e.mNotificationChannelId = NOTIFICATION_CHANNEL_ID; + return e; + } + + private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp); + e.mClass = ACTIVITY_NAME; + e.mLocusId = locusId; + return e; + } + + private static UsageEvents.Event createActivityStoppedEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, timestamp); + e.mClass = ACTIVITY_NAME; + return e; + } + + private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) { + UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp); + e.mPackage = PKG_NAME; + return e; + } + + private static Event createInAppConversationEvent(long timestamp, int durationSeconds) { + return new Event.Builder(timestamp, Event.TYPE_IN_APP_CONVERSATION) + .setDurationSeconds(durationSeconds) + .build(); + } + + private static class TestConversationStore extends ConversationStore { + + private ConversationInfo mConversationInfo; + + @Override + @Nullable + ConversationInfo getConversation(@Nullable String shortcutId) { + return mConversationInfo; + } + } + + private static class TestPackageData extends PackageData { + + private final TestConversationStore mConversationStore = new TestConversationStore(); + 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); + } + + @Override + @NonNull + ConversationStore getConversationStore() { + return mConversationStore; + } + + @Override + @NonNull + EventStore getEventStore() { + return mEventStore; + } + } + + private static class TestEventStore extends EventStore { + + private final EventHistoryImpl mShortcutEventHistory = new TestEventHistoryImpl(); + private final EventHistoryImpl mLocusEventHistory = new TestEventHistoryImpl(); + + @Override + @NonNull + EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { + return mShortcutEventHistory; + } + + @Override + @NonNull + EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { + return mLocusEventHistory; + } + } + + private static class TestEventHistoryImpl extends EventHistoryImpl { + + private final List<Event> mEvents = new ArrayList<>(); + + @Override + @NonNull + public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) { + return mEvents; + } + + @Override + void addEvent(Event event) { + mEvents.add(event); + } + } +} 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 41416f1352a8..3d190be8888b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -52,6 +52,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; @@ -1600,6 +1601,22 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a shortcut with an ID and a locus ID. + */ + protected ShortcutInfo makeShortcutWithLocusId(String id, LocusId locusId) { + final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) + .setActivity(new ComponentName(mClientContext.getPackageName(), "main")) + .setShortLabel("title-" + id) + .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class)) + .setLocusId(locusId); + final ShortcutInfo s = b.build(); + + s.setTimestamp(mInjectedCurrentTimeMillis); // HACK + + return s; + } + + /** * Make an intent. */ protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { @@ -1618,6 +1635,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a LocusId. + */ + protected LocusId makeLocusId(String id) { + return new LocusId(id); + } + + /** * Make an component name, with the client context. */ @NonNull @@ -1955,16 +1979,17 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static ShortcutQuery buildQuery(long changedSince, String packageName, ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { - return buildQuery(changedSince, packageName, null, componentName, flags); + return buildQuery(changedSince, packageName, null, null, componentName, flags); } protected static ShortcutQuery buildQuery(long changedSince, - String packageName, List<String> shortcutIds, ComponentName componentName, - /* @ShortcutQuery.QueryFlags */ int flags) { + String packageName, List<String> shortcutIds, List<LocusId> locusIds, + ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { final ShortcutQuery q = new ShortcutQuery(); q.setChangedSince(changedSince); q.setPackage(packageName); q.setShortcutIds(shortcutIds); + q.setLocusIds(locusIds); q.setActivity(componentName); q.setQueryFlags(flags); return q; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index bfe0c15ef6e8..d4edab44bae3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -24,6 +24,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.content.pm.PackageInstaller; +import android.platform.test.annotations.Presubmit; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -57,6 +58,7 @@ import java.util.Arrays; import java.util.List; @RunWith(AndroidJUnit4.class) +@Presubmit public class PackageInstallerSessionTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 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 798420ee0137..63da5fbab122 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1372,7 +1372,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { setCaller(CALLING_PACKAGE_1); final ShortcutInfo s1_1 = makeShortcut("s1"); - final ShortcutInfo s1_2 = makeShortcut("s2"); + final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1")); assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2))); @@ -1394,7 +1394,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getCallerShortcut("s4").setTimestamp(500); setCaller(CALLING_PACKAGE_3); - final ShortcutInfo s3_2 = makeShortcut("s3"); + final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2")); assertTrue(mManager.setDynamicShortcuts(list(s3_2))); getCallerShortcut("s3").setTimestamp(START_TIME + 5000); @@ -1446,7 +1446,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // With ID. assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), + /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), /* locusIds =*/ null, /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())), @@ -1454,20 +1454,51 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"), - /* activity =*/ null, + /* locusIds =*/ null, /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())), "s2", "s3")))); assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"), + /* locusIds =*/ null, /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())) + /* empty */)))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_2, list(), /* locusIds =*/ null, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())) + /* empty */)))); + + // With locus ID. + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, + list(makeLocusId("l2")), /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())), + "s3")))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null, + list(makeLocusId("l1"), makeLocusId("l2"), makeLocusId("l3")), /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())), + "s2")))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null, + list(makeLocusId("lx1"), makeLocusId("lx2")), /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())) /* empty */)))); assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 1000, CALLING_PACKAGE_2, list(), + /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, list(), /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())) @@ -1498,7 +1529,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, /* package= */ null, list("id"), + /* time =*/ 0, /* package= */ null, list("id"), /* locusIds =*/ null, /* activity =*/ null, /* flags */ 0), getCallingUser()); }); @@ -1537,7 +1568,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, /* package= */ null, list("id"), + /* time =*/ 0, /* package= */ null, list("id"), /* locusIds= */ null, /* activity =*/ null, /* flags */ 0), getCallingUser()); }); diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index d0d2edc59861..64d05f07e64e 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -119,7 +119,7 @@ public class RollbackStoreTest { @Test public void createNonStaged() { - Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); + Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null); assertThat(rollback.getBackupDir().getAbsolutePath()) .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID); @@ -132,7 +132,7 @@ public class RollbackStoreTest { @Test public void createStaged() { - Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER); + Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER, null); assertThat(rollback.getBackupDir().getAbsolutePath()) .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID); @@ -147,7 +147,7 @@ public class RollbackStoreTest { @Test public void saveAndLoadRollback() { - Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); + Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null); origRb.setRestoreUserDataInProgress(true); origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2)); @@ -197,7 +197,7 @@ public class RollbackStoreTest { @Test public void loadFromJson() throws Exception { - Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); + Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null); expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z")); expectedRb.setRestoreUserDataInProgress(true); @@ -246,7 +246,7 @@ public class RollbackStoreTest { @Test public void saveAndDelete() { - Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); + Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null); RollbackStore.saveRollback(rollback); 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 9719509659ec..804c1b9e09fd 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -302,6 +302,22 @@ public class RollbackUnitTest { assertThat(rollback.notifySessionWithSuccess()).isTrue(); } + @Test + public void allPackagesEnabled() { + int[] sessionIds = new int[]{ 7777, 8888 }; + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER, + sessionIds); + // #allPackagesEnabled returns false when 1 out of 2 packages is enabled. + rollback.info.getPackages().add(newPkgInfoFor(PKG_1, 12, 10, false)); + assertThat(rollback.allPackagesEnabled()).isFalse(); + // #allPackagesEnabled returns false for ApkInApex doesn't count. + rollback.info.getPackages().add(newPkgInfoForApkInApex(PKG_3, 157, 156)); + assertThat(rollback.allPackagesEnabled()).isFalse(); + // #allPackagesEnabled returns true when 2 out of 2 packages are enabled. + rollback.info.getPackages().add(newPkgInfoFor(PKG_2, 18, 12, true)); + assertThat(rollback.allPackagesEnabled()).isTrue(); + } + private static PackageRollbackInfo newPkgInfoFor( String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), @@ -310,6 +326,20 @@ public class RollbackUnitTest { new SparseLongArray()); } + /** + * TODO: merge newPkgInfoFor and newPkgInfoForApkInApex by using enums to specify + * 1. IS_APK + * 2. IS_APEX + * 3. IS_APK_IN_APEX + */ + private static PackageRollbackInfo newPkgInfoForApkInApex( + String packageName, long fromVersion, long toVersion) { + return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), + new VersionedPackage(packageName, toVersion), + new IntArray(), new ArrayList<>(), false, true, new IntArray(), + new SparseLongArray()); + } + private static class PackageRollbackInfoForPackage implements ArgumentMatcher<PackageRollbackInfo> { private final String mPkg; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index ae5369204428..2eeeb3ebaa2a 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -30,17 +30,16 @@ import static org.mockito.Mockito.when; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; import android.content.pm.PackageManager; -import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; import android.os.TimestampedValue; import androidx.test.runner.AndroidJUnit4; +import com.android.server.timezonedetector.TestHandler; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -81,35 +80,35 @@ public class TimeDetectorServiceTest { } @Test(expected = SecurityException.class) - public void testSuggestPhoneTime_withoutPermission() { + public void testSuggestTelephonyTime_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); + TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion(); try { - mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); + mTimeDetectorService.suggestTelephonyTime(timeSuggestion); fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE), + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString()); } } @Test - public void testSuggestPhoneTime() throws Exception { + public void testSuggestTelephonyTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); - mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); + TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion(); + mTimeDetectorService.suggestTelephonyTime(timeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE), + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion); } @Test(expected = SecurityException.class) @@ -140,7 +139,7 @@ public class TimeDetectorServiceTest { eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } @@ -170,7 +169,7 @@ public class TimeDetectorServiceTest { verify(mMockContext).enforceCallingOrSelfPermission( eq(android.Manifest.permission.SET_TIME), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion); } @@ -187,21 +186,23 @@ public class TimeDetectorServiceTest { @Test public void testAutoTimeDetectionToggle() throws Exception { - mTimeDetectorService.handleAutoTimeDetectionToggle(); + mTimeDetectorService.handleAutoTimeDetectionChanged(); mTestHandler.assertTotalMessagesEnqueued(1); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); + + mStubbedTimeDetectorStrategy.resetCallTracking(); - mTimeDetectorService.handleAutoTimeDetectionToggle(); + mTimeDetectorService.handleAutoTimeDetectionChanged(); mTestHandler.assertTotalMessagesEnqueued(2); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); } - private static PhoneTimeSuggestion createPhoneTimeSuggestion() { - int phoneId = 1234; + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() { + int slotIndex = 1234; TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L); - return new PhoneTimeSuggestion.Builder(phoneId) + return new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(timeValue) .build(); } @@ -219,10 +220,10 @@ public class TimeDetectorServiceTest { private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy { // Call tracking. - private PhoneTimeSuggestion mLastPhoneSuggestion; + private TelephonyTimeSuggestion mLastTelephonySuggestion; private ManualTimeSuggestion mLastManualSuggestion; private NetworkTimeSuggestion mLastNetworkSuggestion; - private boolean mLastAutoTimeDetectionToggleCalled; + private boolean mHandleAutoTimeDetectionChangedCalled; private boolean mDumpCalled; @Override @@ -230,45 +231,40 @@ public class TimeDetectorServiceTest { } @Override - public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) { - resetCallTracking(); - mLastPhoneSuggestion = timeSuggestion; + public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) { + mLastTelephonySuggestion = timeSuggestion; } @Override public void suggestManualTime(ManualTimeSuggestion timeSuggestion) { - resetCallTracking(); mLastManualSuggestion = timeSuggestion; } @Override public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { - resetCallTracking(); mLastNetworkSuggestion = timeSuggestion; } @Override public void handleAutoTimeDetectionChanged() { - resetCallTracking(); - mLastAutoTimeDetectionToggleCalled = true; + mHandleAutoTimeDetectionChangedCalled = true; } @Override public void dump(PrintWriter pw, String[] args) { - resetCallTracking(); mDumpCalled = true; } void resetCallTracking() { - mLastPhoneSuggestion = null; + mLastTelephonySuggestion = null; mLastManualSuggestion = null; mLastNetworkSuggestion = null; - mLastAutoTimeDetectionToggleCalled = false; + mHandleAutoTimeDetectionChangedCalled = false; mDumpCalled = false; } - void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) { - assertEquals(expectedSuggestion, mLastPhoneSuggestion); + void verifySuggestTelephonyTimeCalled(TelephonyTimeSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); } public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) { @@ -279,45 +275,12 @@ public class TimeDetectorServiceTest { assertEquals(expectedSuggestion, mLastNetworkSuggestion); } - void verifyHandleAutoTimeDetectionToggleCalled() { - assertTrue(mLastAutoTimeDetectionToggleCalled); + void verifyHandleAutoTimeDetectionChangedCalled() { + assertTrue(mHandleAutoTimeDetectionChangedCalled); } void verifyDumpCalled() { assertTrue(mDumpCalled); } } - - /** - * A Handler that can track posts/sends and wait for work to be completed. - */ - private static class TestHandler extends Handler { - - private int mMessagesSent; - - TestHandler(Looper looper) { - super(looper); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - mMessagesSent++; - return super.sendMessageAtTime(msg, uptimeMillis); - } - - /** Asserts the number of messages posted or sent is as expected. */ - void assertTotalMessagesEnqueued(int expected) { - assertEquals(expected, mMessagesSent); - } - - /** - * Waits for all currently enqueued work due to be processed to be completed before - * returning. - */ - void waitForEmptyQueue() throws InterruptedException { - while (!getLooper().getQueue().isIdle()) { - Thread.sleep(100); - } - } - } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index d940a6a320f2..803b245889e7 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.fail; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; @@ -52,7 +52,7 @@ public class TimeDetectorStrategyImplTest { */ private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0); - private static final int ARBITRARY_PHONE_ID = 123456; + private static final int ARBITRARY_SLOT_INDEX = 123456; private Script mScript; @@ -62,51 +62,51 @@ public class TimeDetectorStrategyImplTest { } @Test - public void testSuggestPhoneTime_autoTimeEnabled() { + public void testSuggestTelephonyTime_autoTimeEnabled() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); mScript.simulateTimePassing() - .simulatePhoneTimeSuggestion(timeSuggestion); + .simulateTelephonyTimeSuggestion(timeSuggestion); long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); } @Test - public void testSuggestPhoneTime_emptySuggestionIgnored() { + public void testSuggestTelephonyTime_emptySuggestionIgnored() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, null); - mScript.simulatePhoneTimeSuggestion(timeSuggestion) + int slotIndex = ARBITRARY_SLOT_INDEX; + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, null); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, null); + .assertLatestTelephonySuggestion(slotIndex, null); } @Test - public void testSuggestPhoneTime_systemClockThreshold() { + public void testSuggestTelephonyTime_systemClockThreshold() { final int systemClockUpdateThresholdMillis = 1000; final int clockIncrementMillis = 100; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThresholdMillis) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; // Send the first time signal. It should be used. { - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS); // Increment the the device clocks to simulate the passage of time. mScript.simulateTimePassing(clockIncrementMillis); @@ -114,151 +114,151 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); } // Now send another time signal, but one that is too similar to the last one and should be // stored, but not used to set the system clock. { int underThresholdMillis = systemClockUpdateThresholdMillis - 1; - PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( - phoneId, mScript.peekSystemClockMillis() + underThresholdMillis); + TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + underThresholdMillis); mScript.simulateTimePassing(clockIncrementMillis) - .simulatePhoneTimeSuggestion(timeSuggestion2) + .simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); } // Now send another time signal, but one that is on the threshold and so should be used. { - PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion( - phoneId, + TelephonyTimeSuggestion timeSuggestion3 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); mScript.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis3 = mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion3); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion3); } } @Test - public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() { + public void testSuggestTelephonyTime_multipleSlotIndexsAndBucketing() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - // There are 2 phones in this test. Phone 2 has a different idea of the current time. - // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when - // multiple phone suggestions are available. - int phone1Id = ARBITRARY_PHONE_ID; - int phone2Id = ARBITRARY_PHONE_ID + 1; - long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; - long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis(); + // There are 2 slotIndexes in this test. slotIndex1 and slotIndex2 have different opinions + // about the current time. slotIndex1 < slotIndex2 (which is important because the strategy + // uses the lowest slotIndex when multiple telephony suggestions are available. + int slotIndex1 = ARBITRARY_SLOT_INDEX; + int slotIndex2 = ARBITRARY_SLOT_INDEX + 1; + long slotIndex1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; + long slotIndex2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis(); - // Make a suggestion with phone2Id. + // Make a suggestion with slotIndex2. { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone1Id, null) - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex1, null) + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } mScript.simulateTimePassing(); - // Now make a different suggestion with phone1Id. + // Now make a different suggestion with slotIndex1. { - PhoneTimeSuggestion phone1TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis); + TelephonyTimeSuggestion slotIndex1TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex1TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex1, slotIndex1TimeSuggestion); } mScript.simulateTimePassing(); - // Make another suggestion with phone2Id. It should be stored but not used because the - // phone1Id suggestion will still "win". + // Make another suggestion with slotIndex2. It should be stored but not used because the + // slotIndex1 suggestion will still "win". { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } - // Let enough time pass that phone1Id's suggestion should now be too old. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS); + // Let enough time pass that slotIndex1's suggestion should now be too old. + mScript.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS); - // Make another suggestion with phone2Id. It should be used because the phoneId1 + // Make another suggestion with slotIndex2. It should be used because the slotIndex1 // is in an older "bucket". { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } } @Test - public void testSuggestPhoneTime_autoTimeDisabled() { + public void testSuggestTelephonyTime_autoTimeDisabled() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(false); - int phoneId = ARBITRARY_PHONE_ID; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); + int slotIndex = ARBITRARY_SLOT_INDEX; + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS); mScript.simulateTimePassing() - .simulatePhoneTimeSuggestion(timeSuggestion) + .simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); } @Test - public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() { + public void testSuggestTelephonyTime_invalidNitzReferenceTimesIgnored() { final int systemClockUpdateThreshold = 2000; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThreshold) .pokeAutoTimeDetectionEnabled(true); long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); - // Initialize the strategy / device with a time set from a phone suggestion. + // Initialize the strategy / device with a time set from a telephony suggestion. mScript.simulateTimePassing(); long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // The UTC time increment should be larger than the system clock update threshold so we // know it shouldn't be ignored for other reasons. @@ -269,11 +269,11 @@ public class TimeDetectorStrategyImplTest { long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1; TimestampedValue<Long> utcTime2 = new TimestampedValue<>( referenceTimeBeforeLastSignalMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion2 = - createPhoneTimeSuggestion(phoneId, utcTime2); - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + TelephonyTimeSuggestion timeSuggestion2 = + createTelephonyTimeSuggestion(slotIndex, utcTime2); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Now supply a new signal that has an obviously bogus reference time : substantially in the // future. @@ -281,36 +281,36 @@ public class TimeDetectorStrategyImplTest { utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1; TimestampedValue<Long> utcTime3 = new TimestampedValue<>( referenceTimeInFutureMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion3 = - createPhoneTimeSuggestion(phoneId, utcTime3); - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + TelephonyTimeSuggestion timeSuggestion3 = + createTelephonyTimeSuggestion(slotIndex, utcTime3); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Just to prove validUtcTimeMillis is valid. long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; TimestampedValue<Long> utcTime4 = new TimestampedValue<>( validReferenceTimeMillis, validUtcTimeMillis); long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4); - PhoneTimeSuggestion timeSuggestion4 = - createPhoneTimeSuggestion(phoneId, utcTime4); - mScript.simulatePhoneTimeSuggestion(timeSuggestion4) + TelephonyTimeSuggestion timeSuggestion4 = + createTelephonyTimeSuggestion(slotIndex, utcTime4); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion4) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion4); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion4); } @Test - public void testSuggestPhoneTime_timeDetectionToggled() { + public void testSuggestTelephonyTime_timeDetectionToggled() { final int clockIncrementMillis = 100; final int systemClockUpdateThreshold = 2000; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThreshold) .pokeAutoTimeDetectionEnabled(false); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); // Simulate time passing. @@ -318,9 +318,9 @@ public class TimeDetectorStrategyImplTest { // Simulate the time signal being received. It should not be used because auto time // detection is off but it should be recorded. - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); @@ -330,17 +330,17 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Turn off auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Receive another valid time signal. // It should be on the threshold and accounting for the clock increments. - PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( - phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); + TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); @@ -350,45 +350,45 @@ public class TimeDetectorStrategyImplTest { // The new time, though valid, should not be set in the system clock because auto time is // disabled. - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); } @Test - public void testSuggestPhoneTime_maxSuggestionAge() { + public void testSuggestTelephonyTime_maxSuggestionAge() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion phoneSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion telephonySuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phoneSuggestion) + mScript.calculateTimeInMillisForNow(telephonySuggestion.getUtcTime()); + mScript.simulateTelephonyTimeSuggestion(telephonySuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedSystemClockMillis /* expectedNetworkBroadcast */) - .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); - // Look inside and check what the strategy considers the current best phone suggestion. - assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion()); + // Look inside and check what the strategy considers the current best telephony suggestion. + assertEquals(telephonySuggestion, mScript.peekBestTelephonySuggestion()); - // Simulate time passing, long enough that phoneSuggestion is now too old. + // Simulate time passing, long enough that telephonySuggestion is now too old. mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS); - // Look inside and check what the strategy considers the current best phone suggestion. It - // should still be the, it's just no longer used. - assertNull(mScript.peekBestPhoneSuggestion()); - mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + // Look inside and check what the strategy considers the current best telephony suggestion. + // It should still be the, it's just no longer used. + assertNull(mScript.peekBestTelephonySuggestion()); + mScript.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); } @Test @@ -413,21 +413,21 @@ public class TimeDetectorStrategyImplTest { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; - // Simulate a phone suggestion. + // Simulate a telephony suggestion. long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion phoneTimeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion telephonyTimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); // Simulate the passage of time. mScript.simulateTimePassing(); long expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime()); + mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -435,7 +435,7 @@ public class TimeDetectorStrategyImplTest { // Switch to manual. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -450,7 +450,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(manualTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -459,14 +459,14 @@ public class TimeDetectorStrategyImplTest { mScript.simulateAutoTimeDetectionToggle(); expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime()); mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Switch back to manual - nothing should happen to the clock. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); } /** @@ -515,19 +515,19 @@ public class TimeDetectorStrategyImplTest { } @Test - public void testSuggestNetworkTime_phoneSuggestionsBeatNetworkSuggestions() { + public void testSuggestNetworkTime_telephonySuggestionsBeatNetworkSuggestions() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); // Three obviously different times that could not be mistaken for each other. long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS; long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis(); - long phoneTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis(); + long telephonyTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis(); // A small increment used to simulate the passage of time, but not enough to interfere with // macro-level time changes associated with suggestion age. final long smallTimeIncrementMillis = 101; - // A network suggestion is made. It should be used because there is no phone suggestion. + // A network suggestion is made. It should be used because there is no telephony suggestion. NetworkTimeSuggestion networkTimeSuggestion1 = mScript.generateNetworkTimeSuggestion(networkTimeMillis1); mScript.simulateTimePassing(smallTimeIncrementMillis) @@ -536,37 +536,37 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null) .assertLatestNetworkSuggestion(networkTimeSuggestion1); assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); // Simulate a little time passing. mScript.simulateTimePassing(smallTimeIncrementMillis) .verifySystemClockWasNotSetAndResetCallTracking(); - // Now a phone suggestion is made. Phone suggestions are prioritized over network + // Now a telephony suggestion is made. Telephony suggestions are prioritized over network // suggestions so it should "win". - PhoneTimeSuggestion phoneTimeSuggestion = - mScript.generatePhoneTimeSuggestion(ARBITRARY_PHONE_ID, phoneTimeMillis); + TelephonyTimeSuggestion telephonyTimeSuggestion = + mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeMillis); mScript.simulateTimePassing(smallTimeIncrementMillis) - .simulatePhoneTimeSuggestion(phoneTimeSuggestion) + .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime())); + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion1); assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2) .verifySystemClockWasNotSetAndResetCallTracking(); - // Now another network suggestion is made. Phone suggestions are prioritized over network - // suggestions so the latest phone suggestion should still "win". + // Now another network suggestion is made. Telephony suggestions are prioritized over + // network suggestions so the latest telephony suggestion should still "win". NetworkTimeSuggestion networkTimeSuggestion2 = mScript.generateNetworkTimeSuggestion(networkTimeMillis2); mScript.simulateTimePassing(smallTimeIncrementMillis) @@ -574,14 +574,14 @@ public class TimeDetectorStrategyImplTest { .verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal - // becomes "too old to use". This should mean that phoneTimeSuggestion is now too old to be - // used but networkTimeSuggestion2 is not. + // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to + // be used but networkTimeSuggestion2 is not. mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2); // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last @@ -591,10 +591,10 @@ public class TimeDetectorStrategyImplTest { mScript.verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); // Toggle auto-time off and on to force the detection logic to run. mScript.simulateAutoTimeDetectionToggle() @@ -606,10 +606,10 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); } /** @@ -760,8 +760,8 @@ public class TimeDetectorStrategyImplTest { return mFakeCallback.peekSystemClockMillis(); } - Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) { - mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion); + Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) { + mTimeDetectorStrategy.suggestTelephonyTime(timeSuggestion); return this; } @@ -806,10 +806,10 @@ public class TimeDetectorStrategyImplTest { } /** - * White box test info: Asserts the latest suggestion for the phone ID is as expected. + * White box test info: Asserts the latest suggestion for the slotIndex is as expected. */ - Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) { - assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId)); + Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) { + assertEquals(expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex)); return this; } @@ -822,11 +822,11 @@ public class TimeDetectorStrategyImplTest { } /** - * White box test info: Returns the phone suggestion that would be used, if any, given the - * current elapsed real time clock and regardless of origin prioritization. + * White box test info: Returns the telephony suggestion that would be used, if any, given + * the current elapsed real time clock and regardless of origin prioritization. */ - PhoneTimeSuggestion peekBestPhoneSuggestion() { - return mTimeDetectorStrategy.findBestPhoneSuggestionForTests(); + TelephonyTimeSuggestion peekBestTelephonySuggestion() { + return mTimeDetectorStrategy.findBestTelephonySuggestionForTests(); } /** @@ -848,15 +848,15 @@ public class TimeDetectorStrategyImplTest { } /** - * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the - * reference time. + * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for + * the reference time. */ - PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) { + TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, Long timeMillis) { TimestampedValue<Long> time = null; if (timeMillis != null) { time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis); } - return createPhoneTimeSuggestion(phoneId, time); + return createTelephonyTimeSuggestion(slotIndex, time); } /** @@ -878,9 +878,9 @@ public class TimeDetectorStrategyImplTest { } } - private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId, + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex, TimestampedValue<Long> utcTime) { - return new PhoneTimeSuggestion.Builder(phoneId) + return new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(utcTime) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java new file mode 100644 index 000000000000..21c9685b05d2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java @@ -0,0 +1,76 @@ +/* + * 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.timezonedetector; + +import static org.junit.Assert.assertEquals; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +/** + * A Handler that can track posts/sends and wait for them to be completed. + */ +public class TestHandler extends Handler { + + private final Object mMonitor = new Object(); + private int mMessagesProcessed = 0; + private int mMessagesSent = 0; + + public TestHandler(Looper looper) { + super(looper); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + synchronized (mMonitor) { + mMessagesSent++; + } + + Runnable callback = msg.getCallback(); + // Have the callback increment the mMessagesProcessed when it is done. It will notify + // any threads waiting for all messages to be processed if appropriate. + Runnable newCallback = () -> { + callback.run(); + synchronized (mMonitor) { + mMessagesProcessed++; + if (mMessagesSent == mMessagesProcessed) { + mMonitor.notifyAll(); + } + } + }; + msg.setCallback(newCallback); + return super.sendMessageAtTime(msg, uptimeMillis); + } + + /** Asserts the number of messages posted or sent is as expected. */ + public void assertTotalMessagesEnqueued(int expected) { + synchronized (mMonitor) { + assertEquals(expected, mMessagesSent); + } + } + + /** + * Waits for all enqueued work to be completed before returning. + */ + public void waitForMessagesToBeProcessed() throws InterruptedException { + synchronized (mMonitor) { + if (mMessagesSent != mMessagesProcessed) { + mMonitor.wait(); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java new file mode 100644 index 000000000000..039c2b4933e9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -0,0 +1,233 @@ +/* + * 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.timezonedetector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.HandlerThread; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.PrintWriter; + +@RunWith(AndroidJUnit4.class) +public class TimeZoneDetectorServiceTest { + + private Context mMockContext; + private StubbedTimeZoneDetectorStrategy mStubbedTimeZoneDetectorStrategy; + + private TimeZoneDetectorService mTimeZoneDetectorService; + private HandlerThread mHandlerThread; + private TestHandler mTestHandler; + + + @Before + public void setUp() { + mMockContext = mock(Context.class); + + // Create a thread + handler for processing the work that the service posts. + mHandlerThread = new HandlerThread("TimeZoneDetectorServiceTest"); + mHandlerThread.start(); + mTestHandler = new TestHandler(mHandlerThread.getLooper()); + + mStubbedTimeZoneDetectorStrategy = new StubbedTimeZoneDetectorStrategy(); + + mTimeZoneDetectorService = new TimeZoneDetectorService( + mMockContext, mTestHandler, mStubbedTimeZoneDetectorStrategy); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + mHandlerThread.join(); + } + + @Test(expected = SecurityException.class) + public void testSuggestTelephonyTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); + + try { + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); + fail(); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), + anyString()); + } + } + + @Test + public void testSuggestTelephonyTimeZone() throws Exception { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); + mTestHandler.assertTotalMessagesEnqueued(1); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), + anyString()); + + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion); + } + + @Test(expected = SecurityException.class) + public void testSuggestManualTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + + try { + mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + fail(); + } finally { + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), + anyString()); + } + } + + @Test + public void testSuggestManualTimeZone() throws Exception { + doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + mTestHandler.assertTotalMessagesEnqueued(1); + + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), + anyString()); + + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion); + } + + @Test + public void testDump() { + when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + mTimeZoneDetectorService.dump(null, null, null); + + verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP)); + mStubbedTimeZoneDetectorStrategy.verifyDumpCalled(); + } + + @Test + public void testAutoTimeZoneDetectionChanged() throws Exception { + mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged(); + mTestHandler.assertTotalMessagesEnqueued(1); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled(); + + mStubbedTimeZoneDetectorStrategy.resetCallTracking(); + + mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged(); + mTestHandler.assertTotalMessagesEnqueued(2); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled(); + } + + private static TelephonyTimeZoneSuggestion createTelephonyTimeZoneSuggestion() { + int slotIndex = 1234; + return new TelephonyTimeZoneSuggestion.Builder(slotIndex) + .setZoneId("TestZoneId") + .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET) + .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE) + .build(); + } + + private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() { + return new ManualTimeZoneSuggestion("TestZoneId"); + } + + private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { + + // Call tracking. + private TelephonyTimeZoneSuggestion mLastTelephonySuggestion; + private ManualTimeZoneSuggestion mLastManualSuggestion; + private boolean mHandleAutoTimeZoneDetectionChangedCalled; + private boolean mDumpCalled; + + @Override + public void suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) { + mLastTelephonySuggestion = timeZoneSuggestion; + } + + @Override + public void suggestManualTimeZone(ManualTimeZoneSuggestion timeZoneSuggestion) { + mLastManualSuggestion = timeZoneSuggestion; + } + + @Override + public void handleAutoTimeZoneDetectionChanged() { + mHandleAutoTimeZoneDetectionChangedCalled = true; + } + + @Override + public void dump(PrintWriter pw, String[] args) { + mDumpCalled = true; + } + + void resetCallTracking() { + mLastTelephonySuggestion = null; + mLastManualSuggestion = null; + mHandleAutoTimeZoneDetectionChangedCalled = false; + mDumpCalled = false; + } + + void verifySuggestTelephonyTimeZoneCalled(TelephonyTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); + } + + public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastManualSuggestion); + } + + void verifyHandleAutoTimeZoneDetectionChangedCalled() { + assertTrue(mHandleAutoTimeZoneDetectionChangedCalled); + } + + void verifyDumpCalled() { + assertTrue(mDumpCalled); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 2429cfc1bcd0..ba309679e47a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -16,20 +16,20 @@ package com.android.server.timezonedetector; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE; - -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; + +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_MEDIUM; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_NONE; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,11 +37,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType; -import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality; -import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion; +import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; import org.junit.Before; import org.junit.Test; @@ -52,113 +52,119 @@ import java.util.Collections; import java.util.LinkedList; /** - * White-box unit tests for {@link TimeZoneDetectorStrategy}. + * White-box unit tests for {@link TimeZoneDetectorStrategyImpl}. */ -public class TimeZoneDetectorStrategyTest { +public class TimeZoneDetectorStrategyImplTest { /** A time zone used for initialization that does not occur elsewhere in tests. */ private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC"; - private static final int PHONE1_ID = 10000; - private static final int PHONE2_ID = 20000; + private static final int SLOT_INDEX1 = 10000; + private static final int SLOT_INDEX2 = 20000; // Suggestion test cases are ordered so that each successive one is of the same or higher score // than the previous. private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] { newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW), + QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, - PHONE_SCORE_MEDIUM), + TELEPHONY_SCORE_MEDIUM), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, - QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_MEDIUM), - newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGH), + QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_MEDIUM), + newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, - PHONE_SCORE_HIGH), + TELEPHONY_SCORE_HIGH), newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_HIGHEST), - newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST), + QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_HIGHEST), + newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGHEST), }; - private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; + private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy; private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback; @Before public void setUp() { mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback(); mTimeZoneDetectorStrategy = - new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback); + new TimeZoneDetectorStrategyImpl(mFakeTimeZoneDetectorStrategyCallback); } @Test - public void testEmptyPhoneSuggestions() { - PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion(); - PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion(); + public void testEmptyTelephonySuggestions() { + TelephonyTimeZoneSuggestion slotIndex1TimeZoneSuggestion = + createEmptySlotIndex1Suggestion(); + TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion = + createEmptySlotIndex2Suggestion(); Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); - script.suggestPhoneTimeZone(phone1TimeZoneSuggestion) + script.suggestTelephonyTimeZone(slotIndex1TimeZoneSuggestion) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, PHONE_SCORE_NONE); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - script.suggestPhoneTimeZone(phone2TimeZoneSuggestion) + QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion, + TELEPHONY_SCORE_NONE); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + script.suggestTelephonyTimeZone(slotIndex2TimeZoneSuggestion) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, PHONE_SCORE_NONE); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - // Phone 1 should always beat phone 2, all other things being equal. - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion, + TELEPHONY_SCORE_NONE); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + // SlotIndex1 should always beat slotIndex2, all other things being equal. + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @Test - public void testFirstPlausiblePhoneSuggestionAcceptedWhenTimeZoneUninitialized() { + public void testFirstPlausibleTelephonySuggestionAcceptedWhenTimeZoneUninitialized() { SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW); - PhoneTimeZoneSuggestion lowQualitySuggestion = - testCase.createSuggestion(PHONE1_ID, "America/New_York"); + QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW); + TelephonyTimeZoneSuggestion lowQualitySuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/New_York"); // The device time zone setting is left uninitialized. Script script = new Script() .initializeAutoTimeZoneDetection(true); // The very first suggestion will be taken. - script.suggestPhoneTimeZone(lowQualitySuggestion) + script.suggestTelephonyTimeZone(lowQualitySuggestion) .verifyTimeZoneSetAndReset(lowQualitySuggestion); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion( + lowQualitySuggestion, testCase.expectedScore); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Another low quality suggestion will be ignored now that the setting is initialized. - PhoneTimeZoneSuggestion lowQualitySuggestion2 = - testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles"); - script.suggestPhoneTimeZone(lowQualitySuggestion2) + TelephonyTimeZoneSuggestion lowQualitySuggestion2 = + testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles"); + script.suggestTelephonyTimeZone(lowQualitySuggestion2) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 = - new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion2 = + new QualifiedTelephonyTimeZoneSuggestion( + lowQualitySuggestion2, testCase.expectedScore); assertEquals(expectedScoredSuggestion2, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion2, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } /** @@ -174,28 +180,28 @@ public class TimeZoneDetectorStrategyTest { script.initializeAutoTimeZoneDetection(false) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); - PhoneTimeZoneSuggestion suggestion = - testCase.createSuggestion(PHONE1_ID, "Europe/London"); - script.suggestPhoneTimeZone(suggestion); + TelephonyTimeZoneSuggestion suggestion = + testCase.createSuggestion(SLOT_INDEX1, "Europe/London"); + script.suggestTelephonyTimeZone(suggestion); // When time zone detection is not enabled, the time zone suggestion will not be set // regardless of the score. script.verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting on should cause the device setting to be set. script.autoTimeZoneDetectionEnabled(true); // When time zone detection is already enabled the suggestion (if it scores highly // enough) should be set immediately. - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { script.verifyTimeZoneSetAndReset(suggestion); } else { script.verifyTimeZoneNotSet(); @@ -203,9 +209,9 @@ public class TimeZoneDetectorStrategyTest { // Assert internal service state. assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting should off should do nothing. script.autoTimeZoneDetectionEnabled(false) @@ -213,20 +219,20 @@ public class TimeZoneDetectorStrategyTest { // Assert internal service state. assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } } @Test - public void testPhoneSuggestionsSinglePhone() { + public void testTelephonySuggestionsSingleSlotId() { Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); for (SuggestionTestCase testCase : TEST_CASES) { - makePhone1SuggestionAndCheckState(script, testCase); + makeSlotIndex1SuggestionAndCheckState(script, testCase); } /* @@ -241,130 +247,133 @@ public class TimeZoneDetectorStrategyTest { Collections.reverse(descendingCasesByScore); for (SuggestionTestCase testCase : descendingCasesByScore) { - makePhone1SuggestionAndCheckState(script, testCase); + makeSlotIndex1SuggestionAndCheckState(script, testCase); } } - private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) { + private void makeSlotIndex1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) { // Give the next suggestion a different zone from the currently set device time zone; String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone(); String suggestionZoneId = "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London"; - PhoneTimeZoneSuggestion zonePhone1Suggestion = - testCase.createSuggestion(PHONE1_ID, suggestionZoneId); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore); - - script.suggestPhoneTimeZone(zonePhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone1Suggestion); + TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion = + testCase.createSuggestion(SLOT_INDEX1, suggestionZoneId); + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion( + zoneSlotIndex1Suggestion, testCase.expectedScore); + + script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } /** - * Tries a set of test cases to see if the phone with the lowest ID is given preference. This - * test also confirms that the time zone setting would only be set if a suggestion is of - * sufficient quality. + * Tries a set of test cases to see if the slotIndex with the lowest numeric value is given + * preference. This test also confirms that the time zone setting would only be set if a + * suggestion is of sufficient quality. */ @Test - public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() { + public void testMultipleSlotIndexSuggestionScoringAndSlotIndexBias() { String[] zoneIds = { "Europe/London", "Europe/Paris" }; - PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion(); - PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion(); - QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, PHONE_SCORE_NONE); - QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, PHONE_SCORE_NONE); + TelephonyTimeZoneSuggestion emptySlotIndex1Suggestion = createEmptySlotIndex1Suggestion(); + TelephonyTimeZoneSuggestion emptySlotIndex2Suggestion = createEmptySlotIndex2Suggestion(); + QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex1Suggestion, + TELEPHONY_SCORE_NONE); + QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion, + TELEPHONY_SCORE_NONE); Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) // Initialize the latest suggestions as empty so we don't need to worry about nulls // below for the first loop. - .suggestPhoneTimeZone(emptyPhone1Suggestion) - .suggestPhoneTimeZone(emptyPhone2Suggestion) + .suggestTelephonyTimeZone(emptySlotIndex1Suggestion) + .suggestTelephonyTimeZone(emptySlotIndex2Suggestion) .resetState(); for (SuggestionTestCase testCase : TEST_CASES) { - PhoneTimeZoneSuggestion zonePhone1Suggestion = - testCase.createSuggestion(PHONE1_ID, zoneIds[0]); - PhoneTimeZoneSuggestion zonePhone2Suggestion = - testCase.createSuggestion(PHONE2_ID, zoneIds[1]); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, + TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion = + testCase.createSuggestion(SLOT_INDEX1, zoneIds[0]); + TelephonyTimeZoneSuggestion zoneSlotIndex2Suggestion = + testCase.createSuggestion(SLOT_INDEX2, zoneIds[1]); + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex1Suggestion, testCase.expectedScore); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion, + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex2Suggestion, testCase.expectedScore); - // Start the test by making a suggestion for phone 1. - script.suggestPhoneTimeZone(zonePhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone1Suggestion); + // Start the test by making a suggestion for slotIndex1. + script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedEmptyPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - // Phone 2 then makes an alternative suggestion with an identical score. Phone 1's + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedEmptySlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + // SlotIndex2 then makes an alternative suggestion with an identical score. SlotIndex1's // suggestion should still "win" if it is above the required threshold. - script.suggestPhoneTimeZone(zonePhone2Suggestion); + script.suggestTelephonyTimeZone(zoneSlotIndex2Suggestion); script.verifyTimeZoneNotSet(); // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - // Phone 1 should always beat phone 2, all other things being equal. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the - // zoneId is different, the time zone setting should be updated if the score is high + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + // SlotIndex1 should always beat slotIndex2, all other things being equal. + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + // Withdrawing slotIndex1's suggestion should leave slotIndex2 as the new winner. Since + // the zoneId is different, the time zone setting should be updated if the score is high // enough. - script.suggestPhoneTimeZone(emptyPhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone2Suggestion); + script.suggestTelephonyTimeZone(emptySlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex2Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedEmptyPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + assertEquals(expectedEmptySlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Reset the state for the next loop. - script.suggestPhoneTimeZone(emptyPhone2Suggestion) + script.suggestTelephonyTimeZone(emptySlotIndex2Suggestion) .verifyTimeZoneNotSet(); - assertEquals(expectedEmptyPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedEmptyPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); + assertEquals(expectedEmptySlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedEmptySlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); } } /** - * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time + * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time * zone is actually necessary. This test proves that the service doesn't assume it knows the * current setting. */ @@ -375,21 +384,21 @@ public class TimeZoneDetectorStrategyTest { SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, - PHONE_SCORE_HIGH); - PhoneTimeZoneSuggestion losAngelesSuggestion = - testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles"); - PhoneTimeZoneSuggestion newYorkSuggestion = - testCase.createSuggestion(PHONE1_ID, "America/New_York"); + TELEPHONY_SCORE_HIGH); + TelephonyTimeZoneSuggestion losAngelesSuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles"); + TelephonyTimeZoneSuggestion newYorkSuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/New_York"); // Initialization. - script.suggestPhoneTimeZone(losAngelesSuggestion) + script.suggestTelephonyTimeZone(losAngelesSuggestion) .verifyTimeZoneSetAndReset(losAngelesSuggestion); // Suggest it again - it should not be set because it is already set. - script.suggestPhoneTimeZone(losAngelesSuggestion) + script.suggestTelephonyTimeZone(losAngelesSuggestion) .verifyTimeZoneNotSet(); // Toggling time zone detection should set the device time zone only if the current setting - // value is different from the most recent phone suggestion. + // value is different from the most recent telephony suggestion. script.autoTimeZoneDetectionEnabled(false) .verifyTimeZoneNotSet() .autoTimeZoneDetectionEnabled(true) @@ -398,7 +407,7 @@ public class TimeZoneDetectorStrategyTest { // Simulate a user turning auto detection off, a new suggestion being made while auto // detection is off, and the user turning it on again. script.autoTimeZoneDetectionEnabled(false) - .suggestPhoneTimeZone(newYorkSuggestion) + .suggestTelephonyTimeZone(newYorkSuggestion) .verifyTimeZoneNotSet(); // Latest suggestion should be used. script.autoTimeZoneDetectionEnabled(true) @@ -433,15 +442,16 @@ public class TimeZoneDetectorStrategyTest { return new ManualTimeZoneSuggestion(zoneId); } - private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() { - return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build(); + private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() { + return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build(); } - private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() { - return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build(); + private static TelephonyTimeZoneSuggestion createEmptySlotIndex2Suggestion() { + return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build(); } - static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback { + static class FakeTimeZoneDetectorStrategyCallback + implements TimeZoneDetectorStrategyImpl.Callback { private boolean mAutoTimeZoneDetectionEnabled; private TestState<String> mTimeZoneId = new TestState<>(); @@ -560,13 +570,15 @@ public class TimeZoneDetectorStrategyTest { Script autoTimeZoneDetectionEnabled(boolean enabled) { mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled); - mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange(); + mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged(); return this; } - /** Simulates the time zone detection strategy receiving a phone-originated suggestion. */ - Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) { - mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion); + /** + * Simulates the time zone detection strategy receiving a telephony-originated suggestion. + */ + Script suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) { + mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion); return this; } @@ -581,7 +593,7 @@ public class TimeZoneDetectorStrategyTest { return this; } - Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) { + Script verifyTimeZoneSetAndReset(TelephonyTimeZoneSuggestion suggestion) { mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId()); mFakeTimeZoneDetectorStrategyCallback.commitAllChanges(); return this; @@ -610,8 +622,8 @@ public class TimeZoneDetectorStrategyTest { this.expectedScore = expectedScore; } - private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) { - return new PhoneTimeZoneSuggestion.Builder(phoneId) + private TelephonyTimeZoneSuggestion createSuggestion(int slotIndex, String zoneId) { + return new TelephonyTimeZoneSuggestion.Builder(slotIndex) .setZoneId(zoneId) .setMatchType(matchType) .setQuality(quality) diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 180deb5c4dcc..dab0a5f0e279 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -28,6 +28,8 @@ <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index d16c232afea9..47ad83147262 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -97,7 +97,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); when(mConfig.getConversationNotificationChannel( - any(), anyInt(), eq("a"), eq(r.sbn.getShortcutId(mContext)), eq(true), eq(false))) + any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId(mContext)), eq(true), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 34872db5cf7e..e0ee3ce3aa57 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -30,6 +30,9 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; @@ -67,6 +70,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -684,8 +688,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */, mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(), - nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.getSbn().getTag(), + nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(), + nrBubble.getSbn().getUserId()); waitForIdle(); // Make sure we are a bubble @@ -697,8 +702,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */, mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(), - nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.getSbn().getTag(), + nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(), + nrPlain.getSbn().getUserId()); waitForIdle(); notifsAfter = mBinderService.getActiveNotifications(PKG); @@ -711,8 +717,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { if (summaryAutoCancel) { nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL; } - mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.sbn.getTag(), - nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.getSbn().getTag(), + nrSummary.getSbn().getId(), nrSummary.getSbn().getNotification(), + nrSummary.getSbn().getUserId()); waitForIdle(); notifsAfter = mBinderService.getActiveNotifications(PKG); @@ -891,7 +898,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.createNotificationChannels( PKG, new ParceledListSlice(Arrays.asList(channel))); - final StatusBarNotification sbn = generateNotificationRecord(channel).sbn; + final StatusBarNotification sbn = generateNotificationRecord(channel).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testBlockedNotifications_blockedChannel", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -909,7 +916,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.createNotificationChannels( PKG, new ParceledListSlice(Arrays.asList(channel))); - final StatusBarNotification sbn = generateNotificationRecord(channel).sbn; + final StatusBarNotification sbn = generateNotificationRecord(channel).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -938,7 +945,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel( PKG, mContext.getUserId(), PKG, channel.getId()).getImportance()); - StatusBarNotification sbn = generateNotificationRecord(channel).sbn; + StatusBarNotification sbn = generateNotificationRecord(channel).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -960,7 +967,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel( PKG, mContext.getUserId(), PKG, channel.getId()).getImportance()); - sbn = generateNotificationRecord(channel).sbn; + sbn = generateNotificationRecord(channel).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueuedBlockedNotifications_userBlockedChannelForegroundService", @@ -994,7 +1001,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueuedBlockedNotifications_blockedApp", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -1008,7 +1015,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueuedBlockedNotifications_blockedAppForegroundService", @@ -1031,7 +1038,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { int id = 0; for (String category: categories) { final StatusBarNotification sbn = - generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn; + generateNotificationRecord(mTestNotificationChannel, ++id, "", false).getSbn(); sbn.getNotification().category = category; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueuedRestrictedNotifications_asSystem", @@ -1056,7 +1063,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { int id = 0; for (String category: categories) { final StatusBarNotification sbn = - generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn; + generateNotificationRecord(mTestNotificationChannel, ++id, "", false).getSbn(); sbn.getNotification().category = category; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testEnqueuedRestrictedNotifications_notAutomotive", @@ -1079,7 +1086,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.CATEGORY_CAR_WARNING, Notification.CATEGORY_CAR_INFORMATION); for (String category: categories) { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().category = category; try { mBinderService.enqueueNotificationWithTag(PKG, PKG, @@ -1107,7 +1114,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle bundle = new Bundle(); bundle.putInt(KEY_IMPORTANCE, IMPORTANCE_NONE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), bundle, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), bundle, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); NotificationManagerService.PostNotificationRunnable runnable = @@ -1142,11 +1149,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNull(call.old); assertEquals(0, call.position); assertEquals(0, call.buzzBeepBlink); - assertEquals(PKG, call.r.sbn.getPackageName()); - assertEquals(0, call.r.sbn.getId()); - assertEquals(tag, call.r.sbn.getTag()); - assertNotNull(call.r.sbn.getInstanceId()); - assertEquals(0, call.r.sbn.getInstanceId().getId()); + assertEquals(PKG, call.r.getSbn().getPackageName()); + assertEquals(0, call.r.getSbn().getId()); + assertEquals(tag, call.r.getSbn().getTag()); + assertNotNull(call.r.getSbn().getInstanceId()); + assertEquals(0, call.r.getSbn().getInstanceId().getId()); } @Test @@ -1168,14 +1175,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals( NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.sbn.getInstanceId().getId()); + assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); assertTrue(mNotificationRecordLogger.get(1).shouldLog()); assertEquals( NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_UPDATED, mNotificationRecordLogger.get(1).getUiEvent()); // Instance ID doesn't change on update of an active notification - assertEquals(0, mNotificationRecordLogger.get(1).r.sbn.getInstanceId().getId()); + assertEquals(0, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); } @Test @@ -1209,14 +1216,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals( NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.sbn.getInstanceId().getId()); + assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); assertTrue(mNotificationRecordLogger.get(1).shouldLog()); assertEquals( NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, mNotificationRecordLogger.get(1).getUiEvent()); // New instance ID because notification was canceled before re-post - assertEquals(1, mNotificationRecordLogger.get(1).r.sbn.getInstanceId().getId()); + assertEquals(1, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); } @Test @@ -1257,7 +1264,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception { NotificationRecord r = generateNotificationRecord(null); - final StatusBarNotification sbn = r.sbn; + final StatusBarNotification sbn = r.getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelNotificationsFromListenerImmediatelyAfterEnqueue", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -1271,7 +1278,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotificationsImmediatelyAfterEnqueue", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -1286,7 +1293,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", @@ -1304,14 +1311,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testUserInitiatedClearAll_noLeak", - n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId()); + n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId()); waitForIdle(); mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(n.sbn.getPackageName()); + mBinderService.getActiveNotifications(n.getSbn().getPackageName()); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class); @@ -1328,20 +1335,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotificationsCancelsChildren", - parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId()); + parent.getSbn().getId(), parent.getSbn().getNotification(), + parent.getSbn().getUserId()); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotificationsCancelsChildren", - child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId()); + child.getSbn().getId(), child.getSbn().getNotification(), + child.getSbn().getUserId()); waitForIdle(); - mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId()); + mBinderService.cancelAllNotifications(PKG, parent.getSbn().getUserId()); waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); } @Test public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); for (int i = 0; i < 10; i++) { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotificationsMultipleEnqueuedDoesNotCrash", @@ -1365,20 +1374,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // fully post parent notification mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash", - parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId()); + parent.getSbn().getId(), parent.getSbn().getNotification(), + parent.getSbn().getUserId()); waitForIdle(); // enqueue the child several times for (int i = 0; i < 10; i++) { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash", - child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId()); + child.getSbn().getId(), child.getSbn().getNotification(), + child.getSbn().getUserId()); } // make the parent a child, which will cancel the child notification mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash", - parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(), - parentAsChild.sbn.getUserId()); + parentAsChild.getSbn().getId(), parentAsChild.getSbn().getNotification(), + parentAsChild.getSbn().getUserId()); waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); @@ -1395,7 +1406,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey()); mService.updateAutobundledSummaryFlags(0, "pkg", true, false); - assertTrue(summary.sbn.isOngoing()); + assertTrue(summary.getSbn().isOngoing()); } @Test @@ -1411,12 +1422,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.updateAutobundledSummaryFlags(0, "pkg", false, false); - assertFalse(summary.sbn.isOngoing()); + assertFalse(summary.getSbn().isOngoing()); } @Test public void testCancelAllNotifications_IgnoreForegroundService() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotifications_IgnoreForegroundService", @@ -1431,7 +1442,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotifications_IgnoreOtherPackages", @@ -1446,7 +1457,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotifications_NullPkgRemovesAll", sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -1460,7 +1471,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCancelAllNotifications_NullPkgIgnoresUserAllNotifications", sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL); @@ -1475,7 +1486,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testAppInitiatedCancelAllNotifications_CancelsNoClearFlag", @@ -1497,7 +1508,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getUserId(), 0, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.sbn.getPackageName()); + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1512,7 +1523,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.sbn.getPackageName()); + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1534,7 +1545,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1557,7 +1568,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { parent.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1582,7 +1593,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAfterSecondEnqueueDoesNotSpecifyForegroundFlag() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE; mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), @@ -1616,7 +1627,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1636,12 +1647,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - String[] keys = {parent.sbn.getKey(), child.sbn.getKey(), - child2.sbn.getKey(), newGroup.sbn.getKey()}; + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1664,7 +1675,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { parent.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1680,31 +1691,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationRecord group2 = generateNotificationRecord( mTestNotificationChannel, 2, "group2", true); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked", - group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId()); + group2.getSbn().getId(), group2.getSbn().getNotification(), + group2.getSbn().getUserId()); waitForIdle(); // should not be returned final NotificationRecord nonGroup = generateNotificationRecord( mTestNotificationChannel, 3, null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked", - nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId()); + nonGroup.getSbn().getId(), nonGroup.getSbn().getNotification(), + nonGroup.getSbn().getUserId()); waitForIdle(); // same group, child, should be returned final NotificationRecord group1Child = generateNotificationRecord( mTestNotificationChannel, 4, "group1", false); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked", - group1Child.sbn.getId(), - group1Child.sbn.getNotification(), group1Child.sbn.getUserId()); + group1Child.getSbn().getId(), + group1Child.getSbn().getNotification(), group1Child.getSbn().getUserId()); waitForIdle(); List<NotificationRecord> inGroup1 = mService.findGroupNotificationsLocked(PKG, group1.getGroupKey(), - group1.sbn.getUserId()); + group1.getSbn().getUserId()); assertEquals(3, inGroup1.size()); for (NotificationRecord record : inGroup1) { assertTrue(record.getGroupKey().equals(group1.getGroupKey())); - assertTrue(record.sbn.getId() == 1 || record.sbn.getId() == 4); + assertTrue(record.getSbn().getId() == 1 || record.getSbn().getId() == 4); } } @@ -1718,7 +1731,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.sbn.getPackageName()); + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1738,18 +1751,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - String[] keys = {parent.sbn.getKey(), child.sbn.getKey(), - child2.sbn.getKey(), newGroup.sbn.getKey()}; + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(0, notifs.length); } @Test public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mBinderService.enqueueNotificationWithTag(PKG, PKG, "testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag", @@ -1771,7 +1784,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getUserId(), 0, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.sbn.getPackageName()); + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1786,7 +1799,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.sbn.getPackageName()); + mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1808,7 +1821,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1828,12 +1841,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - String[] keys = {parent.sbn.getKey(), child.sbn.getKey(), - child2.sbn.getKey(), newGroup.sbn.getKey()}; + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(0, notifs.length); } @@ -1856,7 +1869,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { parent.getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.sbn.getPackageName()); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); assertEquals(1, notifs.length); } @@ -1990,7 +2003,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testUpdateGroupNotifyCreatorBlock() throws Exception { NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), + eq(PKG), anyInt())) .thenReturn(existing); NotificationChannelGroup updated = new NotificationChannelGroup("id", "name"); @@ -2013,7 +2027,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); existing.setBlocked(true); mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), + eq(PKG), anyInt())) .thenReturn(existing); mBinderService.updateNotificationChannelGroupForPackage( @@ -2033,7 +2048,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception { NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + when(mPreferencesHelper.getNotificationChannelGroup( + eq(existing.getId()), eq(PKG), anyInt())) .thenReturn(existing); mBinderService.updateNotificationChannelGroupForPackage( @@ -2492,7 +2508,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSnoozeRunnable_snoozeGroupChild_onlyChildOfSummary() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); - assertTrue(parent.sbn.getNotification().isGroupSummary()); + assertTrue(parent.getSbn().getNotification().isGroupSummary()); final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); mService.addNotification(parent); @@ -2528,7 +2544,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel, 2, "group", false); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing", - child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId()); + child.getSbn().getId(), child.getSbn().getNotification(), + child.getSbn().getUserId()); waitForIdle(); verify(mSnoozeHelper, times(1)).repostGroupSummary( @@ -2541,7 +2558,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel, 2, null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing", - record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId()); + record.getSbn().getId(), record.getSbn().getNotification(), + record.getSbn().getUserId()); waitForIdle(); verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString()); @@ -2553,7 +2571,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel, 2, "group", true); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostGroupSummary_noUnsnoozing", - parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId()); + parent.getSbn().getId(), parent.getSbn().getNotification(), + parent.getSbn().getUserId()); waitForIdle(); verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString()); @@ -2972,11 +2991,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); NotificationRecord posted = mService.findNotificationLocked( - PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getUserId()); + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); assertFalse(posted.getNotification().isColorized()); } @@ -2995,7 +3014,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false); mService.addNotification(r); - sampleTagToExclude = r.sbn.getTag(); + sampleTagToExclude = r.getSbn().getTag(); sampleIdToExclude = i; } @@ -3231,7 +3250,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "testBumpFGImportance_noChannelChangePreOApp", - Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0); + Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, + 0); mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(), sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId()); @@ -3269,7 +3289,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey()); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied()); - verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.sbn)); + verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.getSbn())); } @Test @@ -3279,12 +3299,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true, NOTIFICATION_LOCATION_UNKNOWN); - verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true))); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true), + eq((true))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false, NOTIFICATION_LOCATION_UNKNOWN); - verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false))); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true), + eq((false))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); } @@ -3296,13 +3318,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); - verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true))); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(false), + eq((true))); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false, NOTIFICATION_LOCATION_UNKNOWN); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); verify(mAssistants).notifyAssistantExpansionChangedLocked( - eq(r.sbn), eq(false), eq((false))); + eq(r.getSbn()), eq(false), eq((false))); } @Test @@ -3322,11 +3345,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {nv}, new NotificationVisibility[]{}); - verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true)); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(true)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {}, new NotificationVisibility[]{nv}); - verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false)); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(false)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); } @@ -3336,8 +3359,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true); - mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.sbn.getTag(), - r.sbn.getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, + mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(), + r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, NotificationStats.DISMISS_SENTIMENT_POSITIVE, nv); waitForIdle(); @@ -3350,8 +3373,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true); - mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.sbn.getTag(), - r.sbn.getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, + mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(), + r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD, NotificationStats.DISMISS_SENTIMENT_NEGATIVE, nv); waitForIdle(); @@ -3373,7 +3396,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyAdjustmentFromAssistant(null, adjustment); waitForIdle(); @@ -3392,7 +3415,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_NONE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); mBinderService.applyAdjustmentFromAssistant(null, adjustment); @@ -3415,7 +3438,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment()); @@ -3433,7 +3456,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); assertEquals(IMPORTANCE_LOW, r.getImportance()); @@ -3452,7 +3475,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); assertEquals(USER_SENTIMENT_NEUTRAL, r.getUserSentiment()); @@ -3472,7 +3495,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyAdjustmentFromAssistant(null, adjustment); waitForIdle(); @@ -3490,7 +3513,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); waitForIdle(); @@ -3508,7 +3531,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment()); @@ -3622,6 +3645,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void updateUriPermissions_posterDoesNotOwnUri() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", + ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1)); + + Notification.Builder nbA = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1)); + NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification( + PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c); + + doThrow(new SecurityException("no access")).when(mUgm) + .grantUriPermissionFromOwner( + any(), anyInt(), any(), any(), anyInt(), anyInt(), anyInt()); + + when(mUgmInternal.newUriPermissionOwner(any())).thenReturn(new Binder()); + mService.updateUriPermissions(recordA, null, mContext.getPackageName(), USER_SYSTEM); + + // yay, no crash + } + + @Test public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); @@ -4078,7 +4128,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); assertEquals(1, captorHide.getValue().size()); - assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName()); // on broadcast, unhide the package mService.simulatePackageDistractionBroadcast( @@ -4086,7 +4136,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); assertEquals(1, captorUnhide.getValue().size()); - assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + assertEquals("a", captorUnhide.getValue().get(0).getSbn().getPackageName()); } @Test @@ -4107,8 +4157,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // should be called only once. verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); assertEquals(2, captorHide.getValue().size()); - assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); - assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName()); + assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName()); + assertEquals("b", captorHide.getValue().get(1).getSbn().getPackageName()); // on broadcast, unhide the package mService.simulatePackageDistractionBroadcast( @@ -4118,8 +4168,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // should be called only once. verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); assertEquals(2, captorUnhide.getValue().size()); - assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); - assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName()); + assertEquals("a", captorUnhide.getValue().get(0).getSbn().getPackageName()); + assertEquals("b", captorUnhide.getValue().get(1).getSbn().getPackageName()); } @Test @@ -4405,7 +4455,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ai.uid = -1; when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); try { mInternalService.enqueueNotification(notReal, "android", 0, 0, "testPostFromAndroidForNonExistentPackage", @@ -4426,7 +4476,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); // unlike the post case, ignore instead of throwing - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); mInternalService.cancelNotification(notReal, "android", 0, 0, "tag", sbn.getId(), sbn.getUserId()); @@ -4457,7 +4507,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mInternalService.removeForegroundServiceFlagFromNotification( - PKG, r.sbn.getId(), r.sbn.getUserId()); + PKG, r.getSbn().getId(), r.getSbn().getUserId()); waitForIdle(); @@ -4476,7 +4526,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); mInternalService.removeForegroundServiceFlagFromNotification( - PKG, r.sbn.getId(), r.sbn.getUserId()); + PKG, r.getSbn().getId(), r.getSbn().getUserId()); waitForIdle(); @@ -4674,7 +4724,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN, modifiedBeforeSending); verify(mAssistants).notifyAssistantSuggestedReplySent( - eq(r.sbn), eq(reply), eq(generatedByAssistant)); + eq(r.getSbn()), eq(reply), eq(generatedByAssistant)); } @Test @@ -4693,7 +4743,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 10, 10, r.getKey(), actionIndex, action, notificationVisibility, generatedByAssistant); verify(mAssistants).notifyAssistantActionClicked( - eq(r.sbn), eq(actionIndex), eq(action), eq(generatedByAssistant)); + eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant)); } @Test @@ -4766,13 +4816,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.sbn.getId(), - r.sbn.getTag(), mUid, 0, + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.getSbn().getId(), + r.getSbn().getTag(), mUid, 0, new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(), new UserHandle(mUid), null, 0); NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(update); - assertNull(update.sbn.getNotification().getSmallIcon()); + assertNull(update.getSbn().getNotification().getSmallIcon()); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey()); @@ -4944,15 +4994,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifs.length); assertTrue((notifs[0].getNotification().flags & FLAG_BUBBLE) != 0); assertTrue(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -4963,15 +5013,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble_noFlag_appNotAllowed"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifs.length); assertEquals((notifs[0].getNotification().flags & FLAG_BUBBLE), 0); assertFalse(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -4990,16 +5040,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Say we're foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn( IMPORTANCE_FOREGROUND); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // if notif isn't configured properly it doesn't get to bubble just because app is // foreground. assertFalse(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -5010,13 +5060,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubbleNotifs_flag_messaging"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes allowed, yes messaging, yes bubble assertTrue(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -5046,8 +5096,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes phone call, yes person, yes foreground service, yes bubble @@ -5079,8 +5129,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), new UserHandle(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes phone call, yes person, NO foreground service, no bubble @@ -5110,8 +5160,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes phone call, yes foreground service, BUT NO person, no bubble @@ -5145,8 +5195,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes person, yes foreground service, BUT NO call, no bubble @@ -5163,13 +5213,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed"); // Post the notification - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // not allowed, no bubble assertFalse(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -5187,13 +5237,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Post the notification - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // no bubble metadata, no bubble assertFalse(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -5205,13 +5255,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed"); // Post the notification - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // channel not allowed, no bubble assertFalse(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); + nr.getSbn().getKey()).getNotification().isBubbleNotification()); } @Test @@ -5277,7 +5327,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes phone call, yes person, yes foreground service, but channel not allowed, no bubble @@ -5288,10 +5338,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCancelAllNotifications_cancelsBubble() throws Exception { final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); - nr.sbn.getNotification().flags |= FLAG_BUBBLE; + nr.getSbn().getNotification().flags |= FLAG_BUBBLE; mService.addNotification(nr); - mBinderService.cancelAllNotifications(PKG, nr.sbn.getUserId()); + mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); @@ -5302,12 +5352,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testAppCancelNotifications_cancelsBubbles() throws Exception { final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); - nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; // Post the notification mBinderService.enqueueNotificationWithTag(PKG, PKG, "testAppCancelNotifications_cancelsBubbles", - nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId()); + nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(), + nrBubble.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); @@ -5315,8 +5366,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, mService.getNotificationRecordCount()); mBinderService.cancelNotificationWithTag(PKG, PKG, - "testAppCancelNotifications_cancelsBubbles", nrBubble.sbn.getId(), - nrBubble.sbn.getUserId()); + "testAppCancelNotifications_cancelsBubbles", nrBubble.getSbn().getId(), + nrBubble.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); @@ -5328,7 +5379,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception { final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); - nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; mService.addNotification(nrNormal); mService.addNotification(nrBubble); @@ -5345,12 +5396,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception { final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); - nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; mService.addNotification(nrNormal); mService.addNotification(nrBubble); - String[] keys = {nrNormal.sbn.getKey(), nrBubble.sbn.getKey()}; + String[] keys = {nrNormal.getSbn().getKey(), nrBubble.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); @@ -5386,7 +5437,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); signals.putInt(KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); - Adjustment adjustment = new Adjustment(r.sbn.getPackageName(), r.getKey(), signals, + Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); mBinderService.applyAdjustmentFromAssistant(null, adjustment); @@ -5469,8 +5520,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbleChanged_false"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // Reset as this is called when the notif is first sent @@ -5499,8 +5550,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Notif that is not a bubble NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 1, null, false); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // Would be a normal notification because wouldn't have met requirements to bubble @@ -5510,9 +5561,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Update the notification to be message style / meet bubble requirements NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel, - nr.sbn.getTag()); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(), - nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); + nr.getSbn().getTag()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.getSbn().getTag(), + nr2.getSbn().getId(), nr2.getSbn().getNotification(), nr2.getSbn().getUserId()); waitForIdle(); // Reset as this is called when the notif is first sent @@ -5535,8 +5586,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Notif that is not a bubble NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // Reset as this is called when the notif is first sent @@ -5563,11 +5614,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); // Bubbles are allowed! - setUpPrefsForBubbles(PKG, nr.sbn.getUserId(), true /* global */, + setUpPrefsForBubbles(PKG, nr.getSbn().getUserId(), true /* global */, true /* app */, true /* channel */); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // NOT suppressed @@ -5601,7 +5652,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testGrantInlineReplyUriPermission_recordExists() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0); mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // A notification exists for the given record @@ -5613,12 +5664,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1); mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); // Grant permission called for the UID of SystemUI under the target user ID verify(mUgm, times(1)).grantUriPermissionFromOwner(any(), - eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(), - eq(nr.sbn.getUserId())); + eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(), + anyInt(), eq(nr.getSbn().getUserId())); } @Test @@ -5634,12 +5686,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { int uid = 0; // sysui on primary user mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); // Grant permission still called if no NotificationRecord exists for the given key verify(mUgm, times(1)).grantUriPermissionFromOwner(any(), - eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(), - eq(nr.sbn.getUserId())); + eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(), + anyInt(), eq(nr.getSbn().getUserId())); } @Test @@ -5648,7 +5701,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, UserHandle.USER_ALL); mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // A notification exists for the given record @@ -5660,12 +5713,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1); mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); // Target user for the grant is USER_ALL instead of USER_SYSTEM verify(mUgm, times(1)).grantUriPermissionFromOwner(any(), - eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(), - eq(UserHandle.USER_SYSTEM)); + eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(), + anyInt(), eq(UserHandle.USER_SYSTEM)); } @Test @@ -5675,7 +5729,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, otherUserId); mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // A notification exists for the given record @@ -5698,11 +5752,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(otherUserUid); mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), uid); + nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(), uid); // Target user for the grant is USER_ALL instead of USER_SYSTEM verify(mUgm, times(1)).grantUriPermissionFromOwner(any(), - eq(otherUserUid), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(), + eq(otherUserUid), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(), anyInt(), eq(otherUserId)); } @@ -5716,15 +5770,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // create an inline record with two uris in it mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri1, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri2, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri2, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey()); assertNotNull(record); // record exists assertEquals(record.getUris().size(), 2); // record has two uris in it - mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid()); + mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), + nr.getSbn().getUid()); // permissionOwner destroyed verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner( @@ -5737,7 +5794,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0); reset(mPackageManager); - mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid()); + mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), + nr.getSbn().getUid()); // no permissionOwner destroyed verify(mUgmInternal, times(0)).revokeUriPermissionFromOwner( @@ -5755,12 +5813,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // create an inline record a uri in it mService.mNotificationDelegate.grantInlineReplyUriPermission( - nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid()); + nr.getKey(), uri1, nr.getSbn().getUser(), nr.getSbn().getPackageName(), + nr.getSbn().getUid()); InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey()); assertNotNull(record); // record exists - mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid()); + mService.mNotificationDelegate.clearInlineReplyUriPermissions( + nr.getKey(), nr.getSbn().getUid()); // permissionOwner destroyed for USER_SYSTEM, not USER_ALL verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner( @@ -5778,8 +5838,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Notification that would typically bubble NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbles_disabled_lowRamDevice"); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled. @@ -5859,20 +5919,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground"); // Modify metadata flags - nr.sbn.getNotification().getBubbleMetadata().setFlags( + nr.getSbn().getNotification().getBubbleMetadata().setFlags( Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); // Ensure we're not foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn( IMPORTANCE_VISIBLE); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes allowed, yes messaging, yes bubble - Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); + Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification(); assertTrue(notif.isBubbleNotification()); // Our flags should have failed since we're not foreground @@ -5889,20 +5949,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground"); // Modify metadata flags - nr.sbn.getNotification().getBubbleMetadata().setFlags( + nr.getSbn().getNotification().getBubbleMetadata().setFlags( Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); // Ensure we are in the foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn( IMPORTANCE_FOREGROUND); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // yes allowed, yes messaging, yes bubble - Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); + Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification(); assertTrue(notif.isBubbleNotification()); // Our flags should have passed since we are foreground @@ -5935,8 +5995,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); // Test: Send the bubble notification - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); // Verify: @@ -5945,7 +6005,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any()); // yes allowed, yes messaging w/shortcut, yes bubble - Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); + Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification(); assertTrue(notif.isBubbleNotification()); // Test: Remove the shortcut @@ -5958,7 +6018,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue()); // We're no longer a bubble - Notification notif2 = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); + Notification notif2 = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification(); assertFalse(notif2.isBubbleNotification()); } @@ -5974,8 +6034,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Dismiss summary final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2, true); - mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(), - nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(), + mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, + nrSummary.getSbn().getTag(), + nrSummary.getSbn().getId(), nrSummary.getUserId(), nrSummary.getKey(), NotificationStats.DISMISSAL_SHADE, NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv); waitForIdle(); @@ -6085,8 +6146,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testNotificationHistory_addNoisyNotification() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, null /* tvExtender */); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); verify(mHistoryManager, times(1)).addNotification(any()); @@ -6168,4 +6229,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNull(mBinderService.getConversationNotificationChannel( PKG, 0, PKG, callsParent.getId(), false, conversationId)); } + + @Test + public void testCorrectCategory_systemOn_appCannotTurnOff() { + int requested = 0; + int system = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS; + + int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS, + system); + + assertEquals(PRIORITY_CATEGORY_CONVERSATIONS, actual); + } + + @Test + public void testCorrectCategory_systemOff_appTurnOff_noChanges() { + int requested = PRIORITY_CATEGORY_CALLS; + int system = PRIORITY_CATEGORY_CALLS; + + int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS, + system); + + assertEquals(PRIORITY_CATEGORY_CALLS, actual); + } + + @Test + public void testCorrectCategory_systemOn_appTurnOn_noChanges() { + int requested = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS; + int system = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS; + + int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS, + system); + + assertEquals(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, actual); + } + + @Test + public void testCorrectCategory_systemOff_appCannotTurnOn() { + int requested = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS; + int system = PRIORITY_CATEGORY_CALLS; + + int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS, + system); + + assertEquals(PRIORITY_CATEGORY_CALLS, actual); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index c1c74da50c7c..bb84b04361ea 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -171,7 +171,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(SOUND_URI); mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND); + NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); resetZenModeHelper(); @@ -1430,7 +1430,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = true, but // RankingHelper should change to false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND); + NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); assertFalse(mHelper.areChannelsBypassingDnd()); @@ -1441,7 +1441,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testSetupNewZenModeHelper_cannotBypass() { // start notification policy off with mAreChannelsBypassingDnd = false - mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0); + mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); assertFalse(mHelper.areChannelsBypassingDnd()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index 8774b639b554..5018166d9ef1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -140,7 +140,7 @@ public class RankingHelperTest extends UiServiceTestCase { .thenReturn(SOUND_URI); mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, - NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND); + NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper, mUsageStats, new String[] {ImportanceExtractor.class.getName()}); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 5841e59ab3a0..3186d539b817 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -169,11 +169,11 @@ public class SnoozeHelperTest extends UiServiceTestCase { public void testCleanupContextShouldRemovePersistedRecord() { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); mSnoozeHelper.snooze(r, "context"); - mSnoozeHelper.cleanupPersistedContext(r.sbn.getKey()); + mSnoozeHelper.cleanupPersistedContext(r.getSbn().getKey()); assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification( r.getUser().getIdentifier(), - r.sbn.getPackageName(), - r.sbn.getKey() + r.getSbn().getPackageName(), + r.getSbn().getKey() )); } @@ -201,7 +201,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { long actualSnoozedUntilDuration = captor.getValue() - SystemClock.elapsedRealtime(); assertTrue(Math.abs(actualSnoozedUntilDuration - 1000) < 250); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); } @Test @@ -211,7 +211,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { verify(mAm, never()).setExactAndAllowWhileIdle( anyInt(), anyLong(), any(PendingIntent.class)); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); } @Test @@ -221,17 +221,17 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); mSnoozeHelper.snooze(r2 , 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); - mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1); + mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1); // 2 = one for each snooze, above, zero for the cancel. verify(mAm, times(2)).cancel(any(PendingIntent.class)); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); } @Test @@ -243,21 +243,21 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r2, 1000); mSnoozeHelper.snooze(r3, 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey())); mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, false); // 3 = once for each snooze above (3), only. verify(mAm, times(3)).cancel(any(PendingIntent.class)); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey())); } @Test @@ -269,21 +269,21 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r2, 1000); mSnoozeHelper.snooze(r3, 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey())); mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, "pkg2"); // 3 = once for each snooze above (3), only. verify(mAm, times(3)).cancel(any(PendingIntent.class)); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey())); } @Test @@ -291,12 +291,12 @@ public class SnoozeHelperTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); mSnoozeHelper.snooze(r, 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); - mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1); + mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); } @Test @@ -306,11 +306,11 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); mSnoozeHelper.snooze(r2 , 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); - mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1); + mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1); mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM); verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r); @@ -507,18 +507,18 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); mSnoozeHelper.snooze(r2, 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); // clear data mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg"); // nothing snoozed; alarms canceled assertFalse(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertFalse(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); // twice for initial snooze, twice for canceling the snooze verify(mAm, times(4)).cancel(any(PendingIntent.class)); } @@ -533,21 +533,21 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r2, 1000); mSnoozeHelper.snooze(r3, 1000); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey())); // clear data mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg"); assertFalse(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); + UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey())); + UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey())); assertTrue(mSnoozeHelper.isSnoozed( - UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey())); + UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey())); // once for each initial snooze, once for canceling one snooze verify(mAm, times(4)).cancel(any(PendingIntent.class)); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index b65476470773..f7b435ed937b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -91,6 +91,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { int priorityCategories = originalPolicy.priorityCategories; int priorityCallSenders = originalPolicy.priorityCallSenders; int priorityMessageSenders = originalPolicy.priorityMessageSenders; + int priorityConversationsSenders = originalPolicy.priorityConversationSenders; int suppressedVisualEffects = originalPolicy.suppressedVisualEffects; priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS; priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; @@ -99,7 +100,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders, - priorityMessageSenders, suppressedVisualEffects, 0); + priorityMessageSenders, suppressedVisualEffects, 0, priorityConversationsSenders); assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy)); } @@ -235,6 +236,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.areChannelsBypassingDnd = false; config.allowCallsFrom = ZenModeConfig.SOURCE_ANYONE; config.allowMessagesFrom = ZenModeConfig.SOURCE_ANYONE; + config.allowConversations = true; + config.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; config.suppressedVisualEffects = 0; return config; @@ -252,6 +255,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.allowReminders = false; config.allowEvents = false; config.areChannelsBypassingDnd = false; + config.allowConversations = false; + config.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE; config.suppressedVisualEffects = 0; return config; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index 32f389a4fa2d..fb1508842c9d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -16,6 +16,16 @@ package com.android.server.notification; +import static android.app.Notification.CATEGORY_CALL; +import static android.app.Notification.CATEGORY_MESSAGE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; @@ -31,11 +41,10 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; -import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.media.AudioAttributes; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.service.notification.ZenModeConfig; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -76,6 +85,16 @@ public class ZenModeFilteringTest extends UiServiceTestCase { return new NotificationRecord(mContext, sbn, c); } + private NotificationRecord getConversationRecord(NotificationChannel c, + StatusBarNotification sbn) { + NotificationRecord r = mock(NotificationRecord.class); + when(r.getCriticality()).thenReturn(CriticalNotificationExtractor.NORMAL); + when(r.getSbn()).thenReturn(sbn); + when(r.getChannel()).thenReturn(c); + when(r.isConversation()).thenReturn(true); + return r; + } + @Test public void testIsMessage() { NotificationRecord r = getNotificationRecord(); @@ -97,14 +116,14 @@ public class ZenModeFilteringTest extends UiServiceTestCase { assertTrue(mZenModeFiltering.isAlarm(r)); r = getNotificationRecord(); - r.sbn.getNotification().category = Notification.CATEGORY_ALARM; + r.getSbn().getNotification().category = Notification.CATEGORY_ALARM; assertTrue(mZenModeFiltering.isAlarm(r)); } @Test public void testIsAlarm_wrongCategory() { NotificationRecord r = getNotificationRecord(); - r.sbn.getNotification().category = Notification.CATEGORY_CALL; + r.getSbn().getNotification().category = CATEGORY_CALL; assertFalse(mZenModeFiltering.isAlarm(r)); } @@ -121,10 +140,10 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testSuppressDNDInfo_yes_VisEffectsAllowed() { NotificationRecord r = getNotificationRecord(); - when(r.sbn.getPackageName()).thenReturn("android"); - when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); + when(r.getSbn().getPackageName()).thenReturn("android"); + when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects() - - SUPPRESSED_EFFECT_STATUS_BAR); + - SUPPRESSED_EFFECT_STATUS_BAR, 0); assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); } @@ -132,9 +151,9 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testSuppressDNDInfo_yes_WrongId() { NotificationRecord r = getNotificationRecord(); - when(r.sbn.getPackageName()).thenReturn("android"); - when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION); - Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()); + when(r.getSbn().getPackageName()).thenReturn("android"); + when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION); + Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0); assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); } @@ -142,9 +161,9 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testSuppressDNDInfo_yes_WrongPackage() { NotificationRecord r = getNotificationRecord(); - when(r.sbn.getPackageName()).thenReturn("android2"); - when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); - Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()); + when(r.getSbn().getPackageName()).thenReturn("android2"); + when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); + Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0); assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); } @@ -152,9 +171,9 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testSuppressDNDInfo_no() { NotificationRecord r = getNotificationRecord(); - when(r.sbn.getPackageName()).thenReturn("android"); - when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); - Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()); + when(r.getSbn().getPackageName()).thenReturn("android"); + when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE); + Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0); assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r)); @@ -164,7 +183,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { @Test public void testSuppressAnything_yes_ZenModeOff() { NotificationRecord r = getNotificationRecord(); - when(r.sbn.getPackageName()).thenReturn("bananas"); + when(r.getSbn().getPackageName()).thenReturn("bananas"); Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()); assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_OFF, policy, r)); @@ -174,12 +193,120 @@ public class ZenModeFilteringTest extends UiServiceTestCase { public void testSuppressAnything_bypass_ZenModeOn() { NotificationRecord r = getNotificationRecord(); r.setCriticality(CriticalNotificationExtractor.CRITICAL); - when(r.sbn.getPackageName()).thenReturn("bananas"); - Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()); + when(r.getSbn().getPackageName()).thenReturn("bananas"); + Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0); assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r)); r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW); assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r)); } + + @Test + public void testConversation_allAllowed() { + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + + NotificationRecord r = getConversationRecord(channel, sbn); + when(r.isConversation()).thenReturn(true); + + Policy policy = new Policy( + PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE); + + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } + + @Test + public void testConversation_importantAllowed_isImportant() { + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + channel.setImportantConversation(true); + + NotificationRecord r = getConversationRecord(channel, sbn); + + Policy policy = new Policy( + PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_IMPORTANT); + + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } + + @Test + public void testConversation_importantAllowed_isNotImportant() { + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + + NotificationRecord r = getConversationRecord(channel, sbn); + + Policy policy = new Policy( + PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_IMPORTANT); + + assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } + + @Test + public void testConversation_noneAllowed_notCallOrMsg() { + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + + NotificationRecord r = getConversationRecord(channel, sbn); + + Policy policy = + new Policy(PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } + + @Test + public void testConversation_noneAllowed_callAllowed() { + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + + NotificationRecord r = getConversationRecord(channel, sbn); + when(r.isCategory(CATEGORY_CALL)).thenReturn(true); + + Policy policy = + new Policy(PRIORITY_CATEGORY_CALLS, + PRIORITY_SENDERS_ANY, 0, 0, CONVERSATION_SENDERS_NONE); + + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } + + @Test + public void testConversation_noneAllowed_msgAllowed() { + when(mMessagingUtil.isMessaging(any())).thenReturn(true); + Notification n = new Notification.Builder(mContext, "a").build(); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n, + UserHandle.SYSTEM, null, 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "me, work"); + + NotificationRecord r = getConversationRecord(channel, sbn); + + Policy policy = + new Policy(PRIORITY_CATEGORY_MESSAGES, + 0, PRIORITY_SENDERS_ANY, 0, CONVERSATION_SENDERS_NONE); + + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 99771b91ee2a..bc2766c8f92f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -142,7 +142,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { + "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" " + "reminders=\"false\" events=\"false\" callsFrom=\"1\" messagesFrom=\"2\" " + "visualScreenOff=\"true\" alarms=\"true\" " - + "media=\"true\" system=\"false\" />\n" + + "media=\"true\" system=\"false\" conversations=\"true\"" + + " conversationsFrom=\"2\"/>\n" + "<automatic ruleId=\"" + EVENTS_DEFAULT_RULE_ID + "\" enabled=\"false\" snoozing=\"false\"" + " name=\"Event\" zen=\"1\"" @@ -218,7 +219,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testZenOff_NoMuteApplied() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_OFF; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | - Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0); + Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); doNothing().when(mZenModeHelperSpy).applyRestrictions(eq(false), anyBoolean(), anyInt()); @@ -232,7 +233,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | - Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0); + Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, false, @@ -244,7 +245,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, true, AudioAttributes.USAGE_ALARM); @@ -260,7 +261,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testTotalSilence() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | - Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0); + Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); // Total silence will silence alarms, media and system noises (but not vibrations) @@ -281,7 +282,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testAlarmsOnly_alarmMediaMuteNotApplied() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); // Alarms only mode will not silence alarms @@ -304,7 +305,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testAlarmsOnly_callsMuteApplied() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); // Alarms only mode will silence calls despite priority-mode config @@ -318,7 +319,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() { // Only audio attributes with SUPPRESIBLE_NEVER can bypass mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false, false, @@ -330,7 +331,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Only audio attributes with SUPPRESIBLE_NEVER can bypass // with special case USAGE_ASSISTANCE_SONIFICATION mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); for (int usage : AudioAttributes.SDK_USAGES) { @@ -352,7 +353,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testApplyRestrictions_whitelist_priorityOnlyMode() { mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); for (int usage : AudioAttributes.SDK_USAGES) { @@ -367,7 +368,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testApplyRestrictions_whitelist_alarmsOnlyMode() { mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_ALARMS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); for (int usage : AudioAttributes.SDK_USAGES) { @@ -382,7 +383,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testApplyRestrictions_whitelist_totalSilenceMode() { mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_NO_INTERRUPTIONS; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); for (int usage : AudioAttributes.SDK_USAGES) { @@ -401,7 +402,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0); mZenModeHelperSpy.mIsBootComplete = true; - mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0); + mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mZenModeHelperSpy, times(1)).createZenUpgradeNotification(); @@ -452,7 +453,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.mConfig.allowCalls = false; mZenModeHelperSpy.mConfig.allowMessages = false; mZenModeHelperSpy.mConfig.allowEvents = false; - mZenModeHelperSpy.mConfig.allowRepeatCallers= false; + mZenModeHelperSpy.mConfig.allowRepeatCallers = false; + mZenModeHelperSpy.mConfig.allowConversations = false; // 2. apply priority only zen - verify ringer is unchanged mZenModeHelperSpy.applyZenToRingerMode(); @@ -509,7 +511,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.mConfig.allowCalls = false; mZenModeHelperSpy.mConfig.allowMessages = false; mZenModeHelperSpy.mConfig.allowEvents = false; - mZenModeHelperSpy.mConfig.allowRepeatCallers= false; + mZenModeHelperSpy.mConfig.allowRepeatCallers = false; + mZenModeHelperSpy.mConfig.allowConversations = false; ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted = mZenModeHelperSpy.new RingerModeDelegate(); @@ -694,7 +697,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.mConfig.allowCalls = true; mZenModeHelperSpy.mConfig.allowMessages = true; mZenModeHelperSpy.mConfig.allowEvents = true; - mZenModeHelperSpy.mConfig.allowRepeatCallers= true; + mZenModeHelperSpy.mConfig.allowRepeatCallers = true; + mZenModeHelperSpy.mConfig.allowConversations = true; + mZenModeHelperSpy.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE; mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE; mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule(); mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a"); @@ -716,7 +721,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.mConfig.allowCalls = true; mZenModeHelperSpy.mConfig.allowMessages = true; mZenModeHelperSpy.mConfig.allowEvents = true; - mZenModeHelperSpy.mConfig.allowRepeatCallers= true; + mZenModeHelperSpy.mConfig.allowRepeatCallers = true; + mZenModeHelperSpy.mConfig.allowConversations = true; + mZenModeHelperSpy.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE; mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE; mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule(); mZenModeHelperSpy.mConfig.manualRule.zenMode = diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 8c4544249b5c..123bb079dbbb 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -37,6 +37,7 @@ <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.STATUS_BAR" /> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" 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 399cf49ac06f..135d00586329 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -44,6 +45,7 @@ import android.testing.DexmakerShareClassLoaderRule; import androidx.test.filters.SmallTest; +import com.android.internal.app.BlockedAppActivity; import com.android.internal.app.HarmfulAppWarningActivity; import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; @@ -105,6 +107,8 @@ public class ActivityStartInterceptorTest { private PackageManagerService mPackageManager; @Mock private ActivityManagerInternal mAmInternal; + @Mock + private LockTaskController mLockTaskController; private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); @@ -145,6 +149,13 @@ public class ActivityStartInterceptorTest { when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) .thenReturn(null); + // Mock LockTaskController + mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; + when(mService.getLockTaskController()).thenReturn(mLockTaskController); + when(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) + .thenReturn(true); + // Initialise activity info mAInfo.applicationInfo = new ApplicationInfo(); mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; @@ -196,6 +207,18 @@ public class ActivityStartInterceptorTest { } @Test + public void testInterceptLockTaskModeViolationPackage() { + when(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) + .thenReturn(false); + + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + + assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME) + .filterEquals(mInterceptor.mIntent)); + } + + @Test public void testInterceptQuietProfile() { // GIVEN that the user the activity is starting as is currently in quiet mode when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 5acc0f273554..956c200022e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -126,7 +126,8 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget = new TestDragDropController(mWm, mWm.mH.getLooper()); mWindow = createDropTargetWindow("Drag test window", 0); doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); - when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); + when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class), + any(InputChannel.class))).thenReturn(true); mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } @@ -176,7 +177,8 @@ public class DragDropControllerTests extends WindowTestsBase { .setFormat(PixelFormat.TRANSLUCENT) .build(); - assertTrue(mWm.mInputManager.transferTouchFocus(null, null)); + assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(), + new InputChannel())); mToken = mTarget.performDrag( new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data); diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index 039ff604f3f1..a137cde2d351 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -25,10 +25,14 @@ import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; import static android.app.StatusBarManager.DISABLE_HOME; import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS; import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.os.Process.SYSTEM_UID; import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; @@ -693,6 +697,45 @@ public class LockTaskControllerTest { assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); } + @Test + public void testIsActivityAllowed() { + // WHEN lock task mode is not enabled + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // Start lock task mode + Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK is not enabled + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // Enable LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK feature + mLockTaskController.updateLockTaskFeatures( + TEST_USER_ID, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK); + + // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS)); + + // unwhitelisted package should not be allowed + assertFalse(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // update the whitelist + String[] whitelist = new String[] { TEST_PACKAGE_NAME }; + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); + + // whitelisted package should be allowed + assertTrue(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)); + + // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed + assertFalse(mLockTaskController.isActivityAllowed( + TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER)); + } + private Task getTask(int lockTaskAuth) { return getTask(TEST_PACKAGE_NAME, lockTaskAuth); } 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 078347e96a07..7172a1b14244 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -26,16 +26,19 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 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 org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; @@ -139,6 +142,52 @@ public class TaskOrganizerTests extends WindowTestsBase { } @Test + public void testUnregisterOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer).taskAppeared(any()); + assertTrue(stack.isControlledByTaskOrganizer()); + + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); + verify(organizer).taskVanished(any()); + assertFalse(stack.isControlledByTaskOrganizer()); + } + + @Test + public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final ActivityStack stack3 = createTaskStackOnDisplay(mDisplayContent); + final Task task3 = createTaskInStack(stack3, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); + + // First organizer is registered, verify a task appears when changing windowing mode + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, times(1)).taskAppeared(any()); + assertTrue(stack.isControlledByTaskOrganizer()); + + // Now we replace the registration and1 verify the new organizer receives tasks + // newly entering the windowing mode. + final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); + stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer2).taskAppeared(any()); + assertTrue(stack2.isControlledByTaskOrganizer()); + + // Now we unregister the second one, the first one should automatically be reregistered + // so we verify that it's now seeing changes. + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); + + stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, times(2)).taskAppeared(any()); + assertTrue(stack3.isControlledByTaskOrganizer()); + } + + @Test public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED); @@ -161,7 +210,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WindowContainerTransaction t = new WindowContainerTransaction(); Rect newBounds = new Rect(10, 10, 100, 100); t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertEquals(newBounds, task.getBounds()); } @@ -176,7 +225,7 @@ public class TaskOrganizerTests extends WindowTestsBase { assertEquals(stack.mRemoteToken, info.stackToken); Rect newBounds = new Rect(10, 10, 100, 100); t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertEquals(newBounds, stack.getBounds()); } @@ -189,7 +238,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WindowContainerTransaction t = new WindowContainerTransaction(); assertTrue(task.isFocusable()); t.setFocusable(stack.mRemoteToken, false); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertFalse(task.isFocusable()); } @@ -318,4 +367,46 @@ public class TaskOrganizerTests extends WindowTestsBase { } return out; } + + @Test + public void testTrivialBLASTCallback() throws RemoteException { + final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stackController1, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + BLASTSyncEngine bse = new BLASTSyncEngine(); + + BLASTSyncEngine.TransactionReadyListener transactionListener = + mock(BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(transactionListener); + bse.addToSyncSet(id, task); + bse.setReady(id); + // Since this task has no windows the sync is trivial and completes immediately. + verify(transactionListener) + .transactionReady(anyInt(), any()); + } + + @Test + public void testBLASTCallbackWithWindow() { + final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stackController1, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); + + BLASTSyncEngine bse = new BLASTSyncEngine(); + + BLASTSyncEngine.TransactionReadyListener transactionListener = + mock(BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(transactionListener); + bse.addToSyncSet(id, task); + bse.setReady(id); + // Since we have a window we have to wait for it to draw to finish sync. + verify(transactionListener, never()) + .transactionReady(anyInt(), any()); + w.finishDrawing(null); + verify(transactionListener) + .transactionReady(anyInt(), any()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java new file mode 100644 index 000000000000..35723abb4310 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -0,0 +1,60 @@ +/* + * 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.content.pm.PackageManager; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class WindowManagerServiceTests extends WindowTestsBase { + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() { + if (!isAutomotive()) { + mExpectedException.expect(UnsupportedOperationException.class); + + mWm.setForceShowSystemBars(true); + } + } + + @Test + public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() { + if (isAutomotive()) { + mExpectedException.none(); + + mWm.setForceShowSystemBars(true); + } + } + + private boolean isAutomotive() { + return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + } +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 672352211089..e520a02c2721 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7761,9 +7761,8 @@ public class TelephonyManager { * * @hide */ - @SystemApi public static final int DEFAULT_PREFERRED_NETWORK_MODE = - RILConstants.DEFAULT_PREFERRED_NETWORK_MODE; + RILConstants.PREFERRED_NETWORK_MODE; /** * Get the preferred network type. @@ -11290,14 +11289,6 @@ public class TelephonyManager { */ public static final int INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF = 2; - /** @hide */ - @IntDef(prefix = { "INDICATION_UPDATE_MODE_" }, value = { - INDICATION_UPDATE_MODE_NORMAL, - INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndicationUpdateMode{} - /** * The indication for signal strength update. * @hide @@ -11328,51 +11319,6 @@ public class TelephonyManager { */ public static final int INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG = 0x10; - /** @hide */ - @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = { - INDICATION_FILTER_SIGNAL_STRENGTH, - INDICATION_FILTER_FULL_NETWORK_STATE, - INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED, - INDICATION_FILTER_LINK_CAPACITY_ESTIMATE, - INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndicationFilters{} - - /** - * Sets radio indication update mode. This can be used to control the behavior of indication - * update from modem to Android frameworks. For example, by default several indication updates - * are turned off when screen is off, but in some special cases (e.g. carkit is connected but - * screen is off) we want to turn on those indications even when the screen is off. - * - * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * - * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX. - * @see #INDICATION_FILTER_SIGNAL_STRENGTH - * @see #INDICATION_FILTER_FULL_NETWORK_STATE - * @see #INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED - * @param updateMode The voice activation state - * @see #INDICATION_UPDATE_MODE_NORMAL - * @see #INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF - * @hide - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void setRadioIndicationUpdateMode(@IndicationFilters int filters, - @IndicationUpdateMode int updateMode) { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) { - telephony.setRadioIndicationUpdateMode(getSubId(), filters, updateMode); - } - } catch (RemoteException ex) { - // This could happen if binder process crashes. - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } - } - } - /** * A test API to override carrier information including mccmnc, imsi, iccid, gid1, gid2, * plmn and spn. This would be handy for, eg, forcing a particular carrier id, carrier's config diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index beb3c8cac41e..168c8b64c194 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1827,14 +1827,6 @@ interface ITelephony { boolean switchSlots(in int[] physicalSlots); /** - * Sets radio indication update mode. This can be used to control the behavior of indication - * update from modem to Android frameworks. For example, by default several indication updates - * are turned off when screen is off, but in some special cases (e.g. carkit is connected but - * screen is off) we want to turn on those indications even when the screen is off. - */ - void setRadioIndicationUpdateMode(int subId, int filters, int mode); - - /** * Returns whether mobile data roaming is enabled on the subscription with id {@code subId}. * * @param subId the subscription id diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 9ac8cb136c6b..c40573b25068 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -233,14 +233,11 @@ public interface RILConstants { /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */ int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33; - /** Default preferred network mode */ - int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF; - @UnsupportedAppUsage int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network()) .filter(list -> !list.isEmpty()) .map(list -> list.get(0)) - .orElse(DEFAULT_PREFERRED_NETWORK_MODE); + .orElse(NETWORK_MODE_WCDMA_PREF); int BAND_MODE_UNSPECIFIED = 0; //"unspecified" (selected by baseband automatically) int BAND_MODE_EURO = 1; //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000) diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java index 14b847fa9ff6..5f95bc124de6 100644 --- a/test-mock/src/android/test/mock/MockPackageManager.java +++ b/test-mock/src/android/test/mock/MockPackageManager.java @@ -1249,12 +1249,4 @@ public class MockPackageManager extends PackageManager { int uid, byte[] certificate, @PackageManager.CertificateInputType int type) { throw new UnsupportedOperationException(); } - - /** - * @hide - */ - @Override - public String getSystemTextClassifierPackageName() { - throw new UnsupportedOperationException(); - } } diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp new file mode 100644 index 000000000000..edd2b435f1da --- /dev/null +++ b/tests/BlobStoreTestUtils/Android.bp @@ -0,0 +1,20 @@ +// 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. + +java_library { + name: "BlobStoreTestUtils", + srcs: ["src/**/*.java"], + static_libs: ["truth-prebuilt"], + platform_apis: true +}
\ No newline at end of file diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java new file mode 100644 index 000000000000..f96766a1d3ad --- /dev/null +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java @@ -0,0 +1,227 @@ +/* + * 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.utils.blob; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.blob.BlobHandle; +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class DummyBlobData { + private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L; + private static final int BUFFER_SIZE_BYTES = 16 * 1024; + + private final Context mContext; + private final Random mRandom; + private final File mFile; + private final long mFileSize; + private final String mLabel; + + byte[] mFileDigest; + long mExpiryTimeMs; + + public DummyBlobData(Context context) { + this(context, new Random(0), "blob_" + System.nanoTime()); + } + + public DummyBlobData(Context context, long fileSize) { + this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label"); + } + + public DummyBlobData(Context context, Random random, String fileName) { + this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label"); + } + + public DummyBlobData(Context context, Random random, String fileName, String label) { + this(context, DEFAULT_SIZE_BYTES, random, fileName, label); + } + + public DummyBlobData(Context context, long fileSize, Random random, String fileName, + String label) { + mContext = context; + mRandom = random; + mFile = new File(mContext.getFilesDir(), fileName); + mFileSize = fileSize; + mLabel = label; + } + + public void prepare() throws Exception { + try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) { + writeRandomData(file, mFileSize); + } + mFileDigest = FileUtils.digest(mFile, "SHA-256"); + mExpiryTimeMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); + } + + public BlobHandle getBlobHandle() throws Exception { + return BlobHandle.createWithSha256(createSha256Digest(mFile), mLabel, + mExpiryTimeMs, "test_tag"); + } + + public long getFileSize() throws Exception { + return mFileSize; + } + + public long getExpiryTimeMillis() { + return mExpiryTimeMs; + } + + public void delete() { + mFile.delete(); + } + + public void writeToSession(BlobStoreManager.Session session) throws Exception { + writeToSession(session, 0, mFileSize); + } + + public void writeToSession(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + session.openWrite(offsetBytes, lengthBytes))) { + copy(in, out, lengthBytes); + } + } + } + + public void writeToFd(FileDescriptor fd, long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new FileOutputStream(fd)) { + copy(in, out, lengthBytes); + } + } + } + + private void copy(InputStream in, OutputStream out, long lengthBytes) throws Exception { + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesWrittern = 0; + while (bytesWrittern < lengthBytes) { + final int toWrite = (bytesWrittern + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesWrittern); + in.read(buffer, 0, toWrite); + out.write(buffer, 0, toWrite); + bytesWrittern += toWrite; + } + } + + public void readFromSessionAndVerifyBytes(BlobStoreManager.Session session, + long offsetBytes, int lengthBytes) throws Exception { + final byte[] expectedBytes = new byte[lengthBytes]; + try (FileInputStream in = new FileInputStream(mFile)) { + read(in, expectedBytes, offsetBytes, lengthBytes); + } + + final byte[] actualBytes = new byte[lengthBytes]; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openWrite(0L, 0L))) { + read(in, actualBytes, offsetBytes, lengthBytes); + } + + assertThat(actualBytes).isEqualTo(expectedBytes); + + } + + private void read(FileInputStream in, byte[] buffer, + long offsetBytes, int lengthBytes) throws Exception { + in.getChannel().position(offsetBytes); + in.read(buffer, 0, lengthBytes); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session) + throws Exception { + readFromSessionAndVerifyDigest(session, 0, mFile.length()); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openWrite(0L, 0L))) { + actualDigest = createSha256Digest(in, offsetBytes, lengthBytes); + } + + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + public void verifyBlob(ParcelFileDescriptor pfd) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + actualDigest = FileUtils.digest(in, "SHA-256"); + } + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + private byte[] createSha256Digest(FileInputStream in, long offsetBytes, long lengthBytes) + throws Exception { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + in.getChannel().position(offsetBytes); + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesRead = 0; + while (bytesRead < lengthBytes) { + int toRead = (bytesRead + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesRead); + toRead = in.read(buffer, 0, toRead); + digest.update(buffer, 0, toRead); + bytesRead += toRead; + } + return digest.digest(); + } + + private byte[] createSha256Digest(File file) throws Exception { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (BufferedInputStream in = new BufferedInputStream( + Files.newInputStream(file.toPath()))) { + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + int bytesRead; + while ((bytesRead = in.read(buffer)) > 0) { + digest.update(buffer, 0, bytesRead); + } + } + return digest.digest(); + } + + private void writeRandomData(RandomAccessFile file, long fileSize) + throws Exception { + long bytesWritten = 0; + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + while (bytesWritten < fileSize) { + mRandom.nextBytes(buffer); + final int toWrite = (bytesWritten + buffer.length <= fileSize) + ? buffer.length : (int) (fileSize - bytesWritten); + file.seek(bytesWritten); + file.write(buffer, 0, toWrite); + bytesWritten += toWrite; + } + } +} diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 5e9ef8efc402..74dfde848191 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,14 +18,11 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], - certificate: "platform", - libs: [ - "android.test.runner", - "android.test.base", - ], static_libs: [ "junit", - "android-support-test", + "androidx.test.runner", + "androidx.test.core", + "androidx.test.ext.junit", "mockito-target-minus-junit4", "truth-prebuilt", "platform-compat-test-rules" diff --git a/tests/PlatformCompatGating/AndroidManifest.xml b/tests/PlatformCompatGating/AndroidManifest.xml index 7f14b83fbc75..c24dc31b7bf3 100644 --- a/tests/PlatformCompatGating/AndroidManifest.xml +++ b/tests/PlatformCompatGating/AndroidManifest.xml @@ -6,6 +6,6 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.tests.gating"/> </manifest> diff --git a/tests/PlatformCompatGating/AndroidTest.xml b/tests/PlatformCompatGating/AndroidTest.xml index c62684837332..0c7485b27fb8 100644 --- a/tests/PlatformCompatGating/AndroidTest.xml +++ b/tests/PlatformCompatGating/AndroidTest.xml @@ -24,7 +24,6 @@ <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.tests.gating"/> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java index dc317f1941c7..c1ce0e99640c 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java @@ -18,8 +18,9 @@ package com.android.tests.gating; import static com.google.common.truth.Truth.assertThat; import android.compat.testing.PlatformCompatChangeRule; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.compat.testing.DummyApi; @@ -81,14 +82,14 @@ public class PlatformCompatGatingTest { @Test @EnableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) public void testDummyGatingPositiveSystemServer() { - assertThat( - DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isTrue(); + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isTrue(); } @Test @DisableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) public void testDummyGatingNegativeSystemServer() { - assertThat( - DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isFalse(); + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isFalse(); } } diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java new file mode 100644 index 000000000000..9b9e5815a588 --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java @@ -0,0 +1,319 @@ +/* + * 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.tests.gating; + +import static android.Manifest.permission.LOG_COMPAT_CHANGE; +import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.compat.Compatibility.ChangeConfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; +import android.os.ServiceManager; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.compat.IPlatformCompat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.HashSet; +import java.util.Set; + +@RunWith(JUnit4.class) +public final class PlatformCompatPermissionsTest { + + // private Context mContext; + private IPlatformCompat mPlatformCompat; + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private Context mContext; + private UiAutomation mUiAutomation; + private PackageManager mPackageManager; + + @Before + public void setUp() { + // mContext; + mPlatformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mUiAutomation = instrumentation.getUiAutomation(); + mContext = instrumentation.getTargetContext(); + + mPackageManager = mContext.getPackageManager(); + } + + @After + public void tearDown() { + + mUiAutomation.dropShellPermissionIdentity(); + } + + @Test + public void reportChange_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChange_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChangeByPackageName_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByPackageName_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void reportChangeByUid_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabled_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_noLogCompatChangeConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabledByPackageName_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_noLogompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByUid_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void setOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void setOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void clearOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverrides("foo.bar"); + } + @Test + public void clearOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverrides("foo.bar"); + } + + @Test + public void clearOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + @Test + public void clearOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + + @Test + public void clearOverride_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + @Test + public void clearOverride_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + + @Test + public void listAllChanges_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.listAllChanges(); + } + @Test + public void listAllChanges_readCompatConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.listAllChanges(); + } +} diff --git a/tests/PlatformCompatGating/test-rules/Android.bp b/tests/PlatformCompatGating/test-rules/Android.bp index 8211ef523ee7..10fa2dc0d7c6 100644 --- a/tests/PlatformCompatGating/test-rules/Android.bp +++ b/tests/PlatformCompatGating/test-rules/Android.bp @@ -19,7 +19,7 @@ java_library { srcs: ["src/**/*.java"], static_libs: [ "junit", - "android-support-test", + "androidx.test.core", "truth-prebuilt", "core-compat-test-rules" ], diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java index 932ec643d478..d6846faa5c00 100644 --- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -16,13 +16,17 @@ package android.compat.testing; +import android.Manifest; import android.app.Instrumentation; +import android.app.UiAutomation; import android.compat.Compatibility; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; -import android.support.test.InstrumentationRegistry; + +import androidx.test.platform.app.InstrumentationRegistry; + import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.IPlatformCompat; @@ -83,12 +87,17 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { @Override public void evaluate() throws Throwable { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + UiAutomation uiAutomation = instrumentation.getUiAutomation(); String packageName = instrumentation.getTargetContext().getPackageName(); IPlatformCompat platformCompat = IPlatformCompat.Stub .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); if (platformCompat == null) { throw new IllegalStateException("Could not get IPlatformCompat service!"); } + uiAutomation.adoptShellPermissionIdentity( + Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG); Compatibility.setOverrides(mConfig); try { platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig), @@ -101,6 +110,7 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { } catch (RemoteException e) { throw new RuntimeException("Could not call IPlatformCompat binder method!", e); } finally { + uiAutomation.dropShellPermissionIdentity(); Compatibility.clearOverrides(); } } 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 82a524be5c1e..032f18240a55 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 @@ -23,6 +23,8 @@ import static org.junit.Assume.assumeTrue; import static org.testng.Assert.assertThrows; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +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; @@ -31,11 +33,18 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedReader; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; /** * Runs the staged rollback tests. + * + * TODO(gavincorkery): Support the verification of logging parents in Watchdog metrics. */ @RunWith(DeviceJUnit4ClassRunner.class) public class StagedRollbackTest extends BaseHostJUnit4Test { @@ -54,6 +63,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; private static final String TEST_SUBDIR = "/subdir/"; @@ -66,8 +76,19 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { private static final String TEST_FILENAME_4 = "one_more.test"; private static final String TEST_STRING_4 = "once more unto the test"; + private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; + private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; + 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); if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } @@ -77,10 +98,13 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); runPhase("testCleanUp"); + mReceiver.start(); } @After public void tearDown() throws Exception { + mReceiver.stop(); + mReceiver.clear(); runPhase("testCleanUp"); if (!getDevice().isAdbRoot()) { @@ -110,6 +134,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(); runPhase("testBadApkOnly_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } @Test @@ -137,6 +171,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // verify rollback committed runPhase("testNativeWatchdogTriggersRollback_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } @Test @@ -171,6 +215,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // verify all available rollbacks have been committed runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } /** @@ -194,6 +248,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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"); @@ -223,6 +287,15 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // Verify rollback was not executed after health check deadline runPhase("testNetworkPassedDoesNotRollback_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertEquals(watchdogEventOccurred(watchdogEvents, null, null, + REASON_EXPLICIT_HEALTH_CHECK, null), false); + } finally { + logcatStream.close(); + } + } /** @@ -288,6 +361,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(); // Verify rollback occurred due to crash of apk-in-apex runPhase("testRollbackApexWithApkCrashing_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } /** @@ -457,4 +540,57 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { return false; } } + + /** + * 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/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index 8f3cb3442f5a..bdfaaa8f11ea 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -45,7 +45,7 @@ public class TaskOrganizerPipTest extends Service { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); try { - ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct); + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, null); } catch (Exception e) { } } diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 3e4f3d818840..efea91ab91f0 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -272,10 +272,24 @@ public class NetworkCapabilitiesTest { netCap.setOwnerUid(123); assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); - assertParcelSane(netCap, 13); + assertParcelSane(netCap, 15); } @Test + public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() { + final NetworkCapabilities netCap = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .setRequestorUid(9304) + .setRequestorPackageName("com.android.test") + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED); + assertParcelingIsLossless(netCap); + netCap.setSSID(TEST_SSID); + assertParcelSane(netCap, 15); + } + + + @Test public void testOemPaid() { NetworkCapabilities nc = new NetworkCapabilities(); // By default OEM_PAID is neither in the unwanted or required lists and the network is not diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 2d5df4f47e00..0628691c3345 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -38,6 +38,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Context; import android.os.PersistableBundle; +import androidx.test.InstrumentationRegistry; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -58,21 +60,26 @@ public class ConnectivityDiagnosticsManagerTest { private static final Executor INLINE_EXECUTOR = x -> x.run(); - @Mock private Context mContext; @Mock private IConnectivityManager mService; @Mock private ConnectivityDiagnosticsCallback mCb; + private Context mContext; private ConnectivityDiagnosticsBinder mBinder; private ConnectivityDiagnosticsManager mManager; + private String mPackageName; + @Before public void setUp() { - mContext = mock(Context.class); + mContext = InstrumentationRegistry.getContext(); + mService = mock(IConnectivityManager.class); mCb = mock(ConnectivityDiagnosticsCallback.class); mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); mManager = new ConnectivityDiagnosticsManager(mContext, mService); + + mPackageName = mContext.getOpPackageName(); } @After @@ -271,7 +278,7 @@ public class ConnectivityDiagnosticsManagerTest { mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); verify(mService).registerConnectivityDiagnosticsCallback( - any(ConnectivityDiagnosticsBinder.class), eq(request)); + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); } @@ -302,7 +309,7 @@ public class ConnectivityDiagnosticsManagerTest { // verify that re-registering is successful mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); verify(mService, times(2)).registerConnectivityDiagnosticsCallback( - any(ConnectivityDiagnosticsBinder.class), eq(request)); + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); } diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 7ede14428a4f..d6bf334ee56a 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -212,7 +212,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(request); manager.requestNetwork(request, callback, handler); @@ -240,7 +241,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req1); manager.requestNetwork(req1, callback, handler); @@ -258,7 +260,8 @@ public class ConnectivityManagerTest { verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); // callback can be registered again - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req2); manager.requestNetwork(req2, callback, handler); @@ -282,7 +285,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request); + when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt(), any())) + .thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2573bbacd9fa..968f5523d818 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,6 +23,8 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; @@ -105,6 +107,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -119,6 +122,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.annotation.NonNull; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -132,6 +136,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.location.LocationManager; import android.net.CaptivePortalData; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; @@ -177,6 +182,7 @@ import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.os.BadParcelableException; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; @@ -187,6 +193,7 @@ import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -218,6 +225,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Vpn; @@ -292,10 +300,13 @@ public class ConnectivityServiceTest { private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000; + private static final long TIMESTAMP = 1234L; + private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; + private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; private MockContext mServiceContext; @@ -327,6 +338,8 @@ public class ConnectivityServiceTest { @Mock AlarmManager mAlarmManager; @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; @Mock IBinder mIBinder; + @Mock LocationManager mLocationManager; + @Mock AppOpsManager mAppOpsManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -412,6 +425,8 @@ public class ConnectivityServiceTest { if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack; if (Context.USER_SERVICE.equals(name)) return mUserManager; if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; + if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; + if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; return super.getSystemService(name); } @@ -558,12 +573,17 @@ public class ConnectivityServiceTest { | NETWORK_VALIDATION_RESULT_PARTIAL; private static final int VALIDATION_RESULT_INVALID = 0; + private static final long DATA_STALL_TIMESTAMP = 10L; + private static final int DATA_STALL_DETECTION_METHOD = 1; + private INetworkMonitor mNetworkMonitor; private INetworkMonitorCallbacks mNmCallbacks; private int mNmValidationResult = VALIDATION_RESULT_BASE; private int mProbesCompleted; private int mProbesSucceeded; private String mNmValidationRedirectUrl = null; + private PersistableBundle mValidationExtras = PersistableBundle.EMPTY; + private PersistableBundle mDataStallExtras = PersistableBundle.EMPTY; private boolean mNmProvNotificationRequested = false; private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); @@ -631,12 +651,12 @@ public class ConnectivityServiceTest { } mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); - mNmCallbacks.notifyNetworkTested( - mNmValidationResult, mNmValidationRedirectUrl); + mNmCallbacks.notifyNetworkTestedWithExtras( + mNmValidationResult, mNmValidationRedirectUrl, TIMESTAMP, mValidationExtras); if (mNmValidationRedirectUrl != null) { mNmCallbacks.showProvisioningNotification( - "test_provisioning_notif_action", "com.android.test.package"); + "test_provisioning_notif_action", TEST_PACKAGE_NAME); mNmProvNotificationRequested = true; } } @@ -791,6 +811,11 @@ public class ConnectivityServiceTest { public void expectPreventReconnectReceived() { expectPreventReconnectReceived(TIMEOUT_MS); } + + void notifyDataStallSuspected() throws Exception { + mNmCallbacks.notifyDataStallSuspected( + DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras); + } } /** @@ -970,6 +995,8 @@ public class ConnectivityServiceTest { // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; + private VpnInfo mVpnInfo; + public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, userId); @@ -1041,6 +1068,17 @@ public class ConnectivityServiceTest { mConnected = false; mConfig = null; } + + @Override + public synchronized VpnInfo getVpnInfo() { + if (mVpnInfo != null) return mVpnInfo; + + return super.getVpnInfo(); + } + + private void setVpnInfo(VpnInfo vpnInfo) { + mVpnInfo = vpnInfo; + } } private void mockVpn(int uid) { @@ -2936,7 +2974,7 @@ public class ConnectivityServiceTest { networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); mService.requestNetwork(networkCapabilities, null, 0, null, - ConnectivityManager.TYPE_WIFI); + ConnectivityManager.TYPE_WIFI, TEST_PACKAGE_NAME); }); class NonParcelableSpecifier extends NetworkSpecifier { @@ -2975,31 +3013,12 @@ public class ConnectivityServiceTest { } @Test - public void testNetworkSpecifierUidSpoofSecurityException() throws Exception { - class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable { - @Override - public boolean satisfiedBy(NetworkSpecifier other) { - return true; - } - - @Override - public void assertValidFromUid(int requestorUid) { - throw new SecurityException("failure"); - } - - @Override - public int describeContents() { return 0; } - @Override - public void writeToParcel(Parcel dest, int flags) {} - } - + public void testNetworkRequestUidSpoofSecurityException() throws Exception { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - - UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier(); - NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier( - networkSpecifier).build(); + NetworkRequest networkRequest = newWifiRequestBuilder().build(); TestNetworkCallback networkCallback = new TestNetworkCallback(); + doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); assertThrows(SecurityException.class, () -> { mCm.requestNetwork(networkRequest, networkCallback); }); @@ -6400,7 +6419,7 @@ public class ConnectivityServiceTest { new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); try { mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, request); + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); } catch (IllegalArgumentException expected) { } @@ -6410,14 +6429,16 @@ public class ConnectivityServiceTest { public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { final NetworkRequest wifiRequest = new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); - when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); - verify(mIBinder, timeout(TIMEOUT_MS)) - .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); assertTrue( mService.mConnectivityDiagnosticsCallbacks.containsKey( mConnectivityDiagnosticsCallback)); @@ -6438,10 +6459,12 @@ public class ConnectivityServiceTest { when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); - verify(mIBinder, timeout(TIMEOUT_MS)) - .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); verify(mConnectivityDiagnosticsCallback).asBinder(); assertTrue( mService.mConnectivityDiagnosticsCallbacks.containsKey( @@ -6449,7 +6472,7 @@ public class ConnectivityServiceTest { // Register the same callback again mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); // Block until all other events are done processing. HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); @@ -6458,4 +6481,193 @@ public class ConnectivityServiceTest { mService.mConnectivityDiagnosticsCallbacks.containsKey( mConnectivityDiagnosticsCallback)); } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + assertTrue( + "NetworkStack permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be + // active + final VpnInfo info = new VpnInfo(); + info.ownerUid = Process.myUid(); + info.vpnIface = "interface"; + mMockVpn.setVpnInfo(info); + assertTrue( + "Active VPN permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setAdministratorUids(Arrays.asList(Process.myUid())); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, null, mServiceContext, null, null, + mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested + mMockVpn.disconnect(); + assertTrue( + "NetworkCapabilities administrator uid permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setOwnerUid(Process.myUid()); + nc.setAdministratorUids(Arrays.asList(Process.myUid())); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, null, mServiceContext, null, null, + mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Use wrong pid and uid + assertFalse( + "Permissions allowed when they shouldn't be granted", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithUid, + mContext.getOpPackageName())); + } + + private void setupLocationPermissions( + int targetSdk, boolean locationToggle, String op, String perm) throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = targetSdk; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); + + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); + + when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName()))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + + mServiceContext.setPermission(perm, PERMISSION_GRANTED); + } + + private void setUpConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Connect the cell agent verify that it notifies TestNetworkCallback that it is available + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onConnectivityReport fired + verify(mConnectivityDiagnosticsCallback) + .onConnectivityReport(any(ConnectivityReport.class)); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the + // cellular network agent + mCellNetworkAgent.notifyDataStallSuspected(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onDataStallSuspected fired + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class)); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + final Network n = mCellNetworkAgent.getNetwork(); + final boolean hasConnectivity = true; + mService.reportNetworkConnectivity(n, hasConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onNetworkConnectivityReported fired + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(hasConnectivity)); + + final boolean noConnectivity = false; + mService.reportNetworkConnectivity(n, noConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Wait for onNetworkConnectivityReported to fire + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(noConnectivity)); + } } diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp index 0e32aee93ebd..a251c053e004 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -30,6 +30,7 @@ cc_binary_host { "utils.cpp", ], cflags: [ + //"-DSTATS_SCHEMA_LEGACY", "-Wall", "-Werror", ], diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp index a68c3a208869..12c050d8ef8d 100644 --- a/tools/stats_log_api_gen/java_writer_q.cpp +++ b/tools/stats_log_api_gen/java_writer_q.cpp @@ -175,9 +175,7 @@ int write_java_methods_q_schema( indent.c_str()); fprintf(out, "%s android.util.SparseArray<Float> floatMap = null;\n", indent.c_str()); - fprintf(out, - "%s int keyValuePairSize = LIST_TYPE_OVERHEAD * 5;\n", - indent.c_str()); + fprintf(out, "%s int keyValuePairSize = LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < count; i++) {\n", indent.c_str()); fprintf(out, @@ -360,8 +358,9 @@ int write_java_methods_q_schema( requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT; requiredHelpers |= JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS; fprintf(out, - "%s writeKeyValuePairs(buff, pos, intMap, longMap, stringMap, " - "floatMap);\n", indent.c_str()); + "%s writeKeyValuePairs(buff, pos, (byte) count, intMap, longMap, " + "stringMap, floatMap);\n", + indent.c_str()); fprintf(out, "%s pos += keyValuePairSize;\n", indent.c_str()); break; default: @@ -472,7 +471,8 @@ void write_java_helpers_for_q_schema_methods( } if (requiredHelpers & JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS) { - fprintf(out, "%sprivate static void writeKeyValuePairs(byte[] buff, int pos,\n", + fprintf(out, + "%sprivate static void writeKeyValuePairs(byte[] buff, int pos, byte numPairs,\n", indent.c_str()); fprintf(out, "%s final android.util.SparseIntArray intMap,\n", indent.c_str()); fprintf(out, "%s final android.util.SparseLongArray longMap,\n", indent.c_str()); @@ -483,15 +483,12 @@ void write_java_helpers_for_q_schema_methods( // Start list of lists. fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) 4;\n", indent.c_str()); + fprintf(out, "%s buff[pos + 1] = (byte) numPairs;\n", indent.c_str()); fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); // Write integers. fprintf(out, "%s final int intMapSize = null == intMap ? 0 : intMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) intMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < intMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -509,9 +506,6 @@ void write_java_helpers_for_q_schema_methods( // Write longs. fprintf(out, "%s final int longMapSize = null == longMap ? 0 : longMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) longMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < longMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -529,9 +523,6 @@ void write_java_helpers_for_q_schema_methods( // Write Strings. fprintf(out, "%s final int stringMapSize = null == stringMap ? 0 : stringMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) stringMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < stringMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -556,9 +547,6 @@ void write_java_helpers_for_q_schema_methods( // Write floats. fprintf(out, "%s final int floatMapSize = null == floatMap ? 0 : floatMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) floatMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < floatMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp index c7a34feff94b..285514df5ff3 100644 --- a/tools/stats_log_api_gen/native_writer.cpp +++ b/tools/stats_log_api_gen/native_writer.cpp @@ -22,15 +22,6 @@ namespace android { namespace stats_log_api_gen { #if !defined(STATS_SCHEMA_LEGACY) -static void write_native_key_value_pairs_for_type(FILE* out, const int argIndex, - const int typeIndex, const string& type, const string& valueFieldName) { - fprintf(out, " for (const auto& it : arg%d_%d) {\n", argIndex, typeIndex); - fprintf(out, " pairs.push_back(" - "{ .key = it.first, .valueType = %s, .%s = it.second });\n", - type.c_str(), valueFieldName.c_str()); - fprintf(out, " }\n"); - -} static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl, const string& moduleName, const bool supportQ) { @@ -41,7 +32,10 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, continue; } vector<java_type_t> signature = signature_to_modules_it->first; - + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } write_native_method_signature(out, "int stats_write", signature, attributionDecl, " {"); @@ -59,11 +53,6 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, uidName, uidName, tagName); break; } - case JAVA_TYPE_KEY_VALUE_PAIR: - fprintf(out, " event.writeKeyValuePairs(" - "arg%d_1, arg%d_2, arg%d_3, arg%d_4);\n", - argIndex, argIndex, argIndex, argIndex); - break; case JAVA_TYPE_BYTE_ARRAY: fprintf(out, " event.writeByteArray(arg%d.arg, arg%d.arg_length);\n", argIndex, argIndex); @@ -85,7 +74,7 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, fprintf(out, " event.writeString(arg%d);\n", argIndex); break; default: - // Unsupported types: OBJECT, DOUBLE. + // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS. fprintf(stderr, "Encountered unsupported type."); return 1; } @@ -93,8 +82,8 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, } fprintf(out, " return event.writeToSocket();\n"); } else { - fprintf(out, " struct stats_event* event = stats_event_obtain();\n"); - fprintf(out, " stats_event_set_atom_id(event, code);\n"); + fprintf(out, " AStatsEvent* event = AStatsEvent_obtain();\n"); + fprintf(out, " AStatsEvent_setAtomId(event, code);\n"); for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { switch (*arg) { @@ -102,57 +91,43 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, const char* uidName = attributionDecl.fields.front().name.c_str(); const char* tagName = attributionDecl.fields.back().name.c_str(); fprintf(out, - " stats_event_write_attribution_chain(event, " + " AStatsEvent_writeAttributionChain(event, " "reinterpret_cast<const uint32_t*>(%s), %s.data(), " "static_cast<uint8_t>(%s_length));\n", uidName, tagName, uidName); break; } - case JAVA_TYPE_KEY_VALUE_PAIR: - fprintf(out, " std::vector<key_value_pair> pairs;\n"); - write_native_key_value_pairs_for_type( - out, argIndex, 1, "INT32_TYPE", "int32Value"); - write_native_key_value_pairs_for_type( - out, argIndex, 2, "INT64_TYPE", "int64Value"); - write_native_key_value_pairs_for_type( - out, argIndex, 3, "STRING_TYPE", "stringValue"); - write_native_key_value_pairs_for_type( - out, argIndex, 4, "FLOAT_TYPE", "floatValue"); - fprintf(out, - " stats_event_write_key_value_pairs(event, pairs.data(), " - "static_cast<uint8_t>(pairs.size()));\n"); - break; case JAVA_TYPE_BYTE_ARRAY: fprintf(out, - " stats_event_write_byte_array(event, " + " AStatsEvent_writeByteArray(event, " "reinterpret_cast<const uint8_t*>(arg%d.arg), arg%d.arg_length);\n", argIndex, argIndex); break; case JAVA_TYPE_BOOLEAN: - fprintf(out, " stats_event_write_bool(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeBool(event, arg%d);\n", argIndex); break; case JAVA_TYPE_INT: // Fall through. case JAVA_TYPE_ENUM: - fprintf(out, " stats_event_write_int32(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeInt32(event, arg%d);\n", argIndex); break; case JAVA_TYPE_FLOAT: - fprintf(out, " stats_event_write_float(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeFloat(event, arg%d);\n", argIndex); break; case JAVA_TYPE_LONG: - fprintf(out, " stats_event_write_int64(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeInt64(event, arg%d);\n", argIndex); break; case JAVA_TYPE_STRING: - fprintf(out, " stats_event_write_string8(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeString(event, arg%d);\n", argIndex); break; default: - // Unsupported types: OBJECT, DOUBLE. + // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS fprintf(stderr, "Encountered unsupported type."); return 1; } argIndex++; } - fprintf(out, " const int ret = stats_event_write(event);\n"); - fprintf(out, " stats_event_release(event);\n"); + fprintf(out, " const int ret = AStatsEvent_write(event);\n"); + fprintf(out, " AStatsEvent_release(event);\n"); fprintf(out, " return ret;\n"); } fprintf(out, "}\n\n"); @@ -169,6 +144,10 @@ static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms& continue; } vector<java_type_t> signature = signature_it->first; + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } write_native_method_signature(out, "int stats_write_non_chained", signature, attributionDecl, " {"); @@ -210,8 +189,14 @@ static void write_native_method_header( if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) { continue; } - vector<java_type_t> signature = signature_to_modules_it->first; + +#if !defined(STATS_SCHEMA_LEGACY) + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } +#endif write_native_method_signature(out, methodName, signature, attributionDecl, ";"); } } diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp index d6b9d81137ac..fe9a438d81d7 100644 --- a/tools/streaming_proto/cpp/main.cpp +++ b/tools/streaming_proto/cpp/main.cpp @@ -33,13 +33,13 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind if (GENERATE_MAPPING) { string name = make_constant_name(enu.name()); string prefix = name + "_"; - text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl; - text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl; + text << indent << "static const int _ENUM_" << name << "_COUNT = " << N << ";" << endl; + text << indent << "static const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl; } text << indent << "};" << endl; - text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl; + text << indent << "static const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl; } @@ -102,13 +102,13 @@ write_message(stringstream& text, const DescriptorProto& message, const string& if (GENERATE_MAPPING) { N = message.field_size(); - text << indented << "const int _FIELD_COUNT = " << N << ";" << endl; - text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl; + text << indented << "static const int _FIELD_COUNT = " << N << ";" << endl; + text << indented << "static const char* _FIELD_NAMES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl; } text << indented << "};" << endl; - text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl; + text << indented << "static const uint64_t _FIELD_IDS[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl; } @@ -152,7 +152,7 @@ write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& fi write_message(text, file_descriptor.message_type(i), ""); } - for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) { + for (vector<string>::reverse_iterator it = namespaces.rbegin(); it != namespaces.rend(); it++) { text << "} // " << *it << endl; } diff --git a/wifi/Android.bp b/wifi/Android.bp index dae04c6c3a25..6a29b1c36052 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -63,7 +63,6 @@ test_access_hidden_api_whitelist = [ "//frameworks/base/wifi/tests", "//frameworks/opt/net/wifi/tests/wifitests:__subpackages__", - "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests", "//external/robolectric-shadows:__subpackages__", "//frameworks/base/packages/SettingsLib/tests/integ", "//external/sl4a:__subpackages__", diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 91b7df372e01..7c3d0b92dd0a 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -568,14 +568,12 @@ public class WifiConfiguration implements Parcelable { * 2GHz band. * @hide */ - @SystemApi public static final int AP_BAND_2GHZ = 0; /** * 5GHz band. * @hide */ - @SystemApi public static final int AP_BAND_5GHZ = 1; /** @@ -583,7 +581,6 @@ public class WifiConfiguration implements Parcelable { * operating country code and current radio conditions. * @hide */ - @SystemApi public static final int AP_BAND_ANY = -1; /** @@ -593,7 +590,7 @@ public class WifiConfiguration implements Parcelable { * * @hide */ - @SystemApi + @UnsupportedAppUsage @ApBand public int apBand = AP_BAND_2GHZ; @@ -1304,10 +1301,34 @@ public class WifiConfiguration implements Parcelable { public static final int DISABLED_BY_WRONG_PASSWORD = 8; /** This network is disabled because service is not subscribed. */ public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9; - /** All other disable reasons should be strictly less than this value. */ + /** + * All other disable reasons should be strictly less than this value. + * @hide + */ public static final int NETWORK_SELECTION_DISABLED_MAX = 10; /** + * Get an integer that is equal to the maximum integer value of all the + * DISABLED_* reasons + * e.g. {@link #DISABLED_NONE}, {@link #DISABLED_ASSOCIATION_REJECTION}, etc. + * + * All DISABLED_* constants will be contiguous in the range + * 0, 1, 2, 3, ..., getMaxNetworkSelectionDisableReasons() + * + * <br /> + * For example, this can be used to iterate through all the network selection + * disable reasons like so: + * <pre>{@code + * for (int reason = 0; reason <= getMaxNetworkSelectionDisableReasons(); reason++) { + * ... + * } + * }</pre> + */ + public static int getMaxNetworkSelectionDisableReason() { + return NETWORK_SELECTION_DISABLED_MAX - 1; + } + + /** * Contains info about disable reasons. * @hide */ @@ -1709,7 +1730,10 @@ public class WifiConfiguration implements Parcelable { return mStatus; } - /** True if the current network is enabled to join network selection, false otherwise. */ + /** + * True if the current network is enabled to join network selection, false otherwise. + * @hide + */ public boolean isNetworkEnabled() { return mStatus == NETWORK_SELECTION_ENABLED; } @@ -1722,7 +1746,10 @@ public class WifiConfiguration implements Parcelable { return mStatus == NETWORK_SELECTION_TEMPORARY_DISABLED; } - /** True if the current network is permanently disabled, false otherwise. */ + /** + * True if the current network is permanently disabled, false otherwise. + * @hide + */ public boolean isNetworkPermanentlyDisabled() { return mStatus == NETWORK_SELECTION_PERMANENTLY_DISABLED; } diff --git a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java index 002820b1bcc8..1507199b0264 100644 --- a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java +++ b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java @@ -102,15 +102,6 @@ public class WifiFrameworkInitializer { } ); SystemServiceRegistry.registerContextAwareService( - Context.WIFI_RTT_SERVICE, - RttManager.class, - (context, serviceBinder) -> { - IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder); - WifiRttManager wifiRttManager = new WifiRttManager(context, service); - return new RttManager(context, wifiRttManager); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class, (context, serviceBinder) -> { @@ -118,5 +109,13 @@ public class WifiFrameworkInitializer { return new WifiRttManager(context, service); } ); + SystemServiceRegistry.registerContextAwareService( + Context.WIFI_RTT_SERVICE, + RttManager.class, + context -> { + WifiRttManager wifiRttManager = context.getSystemService(WifiRttManager.class); + return new RttManager(context, wifiRttManager); + } + ); } } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 7c031eaaeaf4..24b2a8e8994f 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -449,9 +449,8 @@ public class WifiInfo implements Parcelable { * <p> * If the SSID can be decoded as UTF-8, it will be returned surrounded by double * quotation marks. Otherwise, it is returned as a string of hex digits. - * The SSID may be - * <lt><unknown ssid>, if there is no network currently connected or if the caller has - * insufficient permissions to access the SSID.<lt> + * The SSID may be {@link WifiManager#UNKNOWN_SSID}, if there is no network currently connected + * or if the caller has insufficient permissions to access the SSID. * </p> * <p> * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1dc4a069fe2f..a3cce7c00b3a 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2625,8 +2625,8 @@ public class WifiManager { public void getWifiActivityEnergyInfoAsync( @NonNull @CallbackExecutor Executor executor, @NonNull OnWifiActivityEnergyInfoListener listener) { - if (executor == null) throw new IllegalArgumentException("executor cannot be null"); - if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(listener, "listener cannot be null"); try { mService.getWifiActivityEnergyInfoAsync( new OnWifiActivityEnergyInfoProxy(executor, listener)); diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java index 04d2e1a8b5dd..6632c162fcf9 100644 --- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java @@ -27,7 +27,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import java.util.Objects; @@ -41,33 +40,10 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements */ private final WifiConfiguration mWifiConfiguration; - /** - * The UID of the app that requested a specific wifi network using {@link WifiNetworkSpecifier}. - * - * Will only be filled when the device connects to a wifi network as a result of a - * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to -1 if the device - * auto-connected to a wifi network. - */ - private final int mOriginalRequestorUid; - - /** - * The package name of the app that requested a specific wifi network using - * {@link WifiNetworkSpecifier}. - * - * Will only be filled when the device connects to a wifi network as a result of a - * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device - * auto-connected to a wifi network. - */ - private final String mOriginalRequestorPackageName; - - public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration, - int originalRequestorUid, - @Nullable String originalRequestorPackageName) { + public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration) { checkNotNull(wifiConfiguration); mWifiConfiguration = wifiConfiguration; - mOriginalRequestorUid = originalRequestorUid; - mOriginalRequestorPackageName = originalRequestorPackageName; } /** @@ -78,10 +54,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements @Override public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) { WifiConfiguration wifiConfiguration = in.readParcelable(null); - int originalRequestorUid = in.readInt(); - String originalRequestorPackageName = in.readString(); - return new WifiNetworkAgentSpecifier( - wifiConfiguration, originalRequestorUid, originalRequestorPackageName); + return new WifiNetworkAgentSpecifier(wifiConfiguration); } @Override @@ -98,8 +71,6 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mWifiConfiguration, flags); - dest.writeInt(mOriginalRequestorUid); - dest.writeString(mOriginalRequestorPackageName); } @Override @@ -149,12 +120,6 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements this.mWifiConfiguration.allowedKeyManagement)) { return false; } - if (ns.requestorUid != this.mOriginalRequestorUid) { - return false; - } - if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) { - return false; - } return true; } @@ -163,9 +128,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements return Objects.hash( mWifiConfiguration.SSID, mWifiConfiguration.BSSID, - mWifiConfiguration.allowedKeyManagement, - mOriginalRequestorUid, - mOriginalRequestorPackageName); + mWifiConfiguration.allowedKeyManagement); } @Override @@ -180,10 +143,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements return Objects.equals(this.mWifiConfiguration.SSID, lhs.mWifiConfiguration.SSID) && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID) && Objects.equals(this.mWifiConfiguration.allowedKeyManagement, - lhs.mWifiConfiguration.allowedKeyManagement) - && mOriginalRequestorUid == lhs.mOriginalRequestorUid - && TextUtils.equals(mOriginalRequestorPackageName, - lhs.mOriginalRequestorPackageName); + lhs.mWifiConfiguration.allowedKeyManagement); } @Override @@ -192,19 +152,11 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements sb.append("WifiConfiguration=") .append(", SSID=").append(mWifiConfiguration.SSID) .append(", BSSID=").append(mWifiConfiguration.BSSID) - .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid) - .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName) .append("]"); return sb.toString(); } @Override - public void assertValidFromUid(int requestorUid) { - throw new IllegalStateException("WifiNetworkAgentSpecifier should never be used " - + "for requests."); - } - - @Override public NetworkSpecifier redact() { return null; } diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java index 444e1ef041e8..3d946c9f887d 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java @@ -20,7 +20,6 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Application; import android.net.MacAddress; import android.net.MatchAllNetworkSpecifier; import android.net.NetworkRequest; @@ -28,13 +27,9 @@ import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; -import android.os.Process; import android.text.TextUtils; -import android.util.Log; import android.util.Pair; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -438,24 +433,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc return new WifiNetworkSpecifier( mSsidPatternMatcher, mBssidPatternMatcher, - buildWifiConfiguration(), - Process.myUid(), - getCurrentApplicationReflectively().getApplicationContext().getOpPackageName()); - } - - // TODO(b/144102365): Remove once refactor is complete - private static Application getCurrentApplicationReflectively() { - try { - // reflection for static method android.app.ActivityThread#currentApplication() - Class<?> klass = Class.forName("android.app.ActivityThread"); - Method currentApplicationMethod = klass.getDeclaredMethod("currentApplication"); - Object result = currentApplicationMethod.invoke(null); - return (Application) result; - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException - | InvocationTargetException e) { - Log.e(TAG, "Failed to call ActivityThread#currentApplication() reflectively!", e); - throw new RuntimeException(e); - } + buildWifiConfiguration()); } } @@ -483,20 +461,6 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc */ public final WifiConfiguration wifiConfiguration; - /** - * The UID of the process initializing this network specifier. Validated by receiver using - * checkUidIfNecessary() and is used by satisfiedBy() to determine whether the specifier - * matches the offered network. - * @hide - */ - public final int requestorUid; - - /** - * The package name of the app initializing this network specifier. - * @hide - */ - public final String requestorPackageName; - /** @hide */ public WifiNetworkSpecifier() throws IllegalAccessException { throw new IllegalAccessException("Use the builder to create an instance"); @@ -505,18 +469,14 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc /** @hide */ public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher, @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher, - @NonNull WifiConfiguration wifiConfiguration, - int requestorUid, @NonNull String requestorPackageName) { + @NonNull WifiConfiguration wifiConfiguration) { checkNotNull(ssidPatternMatcher); checkNotNull(bssidPatternMatcher); checkNotNull(wifiConfiguration); - checkNotNull(requestorPackageName); this.ssidPatternMatcher = ssidPatternMatcher; this.bssidPatternMatcher = bssidPatternMatcher; this.wifiConfiguration = wifiConfiguration; - this.requestorUid = requestorUid; - this.requestorPackageName = requestorPackageName; } public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR = @@ -529,10 +489,8 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc Pair<MacAddress, MacAddress> bssidPatternMatcher = Pair.create(baseAddress, mask); WifiConfiguration wifiConfiguration = in.readParcelable(null); - int requestorUid = in.readInt(); - String requestorPackageName = in.readString(); return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, - wifiConfiguration, requestorUid, requestorPackageName); + wifiConfiguration); } @Override @@ -552,18 +510,13 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc dest.writeParcelable(bssidPatternMatcher.first, flags); dest.writeParcelable(bssidPatternMatcher.second, flags); dest.writeParcelable(wifiConfiguration, flags); - dest.writeInt(requestorUid); - dest.writeString(requestorPackageName); } @Override public int hashCode() { return Objects.hash( - ssidPatternMatcher.getPath(), - ssidPatternMatcher.getType(), - bssidPatternMatcher, - wifiConfiguration.allowedKeyManagement, - requestorUid, requestorPackageName); + ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher, + wifiConfiguration.allowedKeyManagement); } @Override @@ -582,9 +535,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc && Objects.equals(this.bssidPatternMatcher, lhs.bssidPatternMatcher) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, - lhs.wifiConfiguration.allowedKeyManagement) - && requestorUid == lhs.requestorUid - && TextUtils.equals(requestorPackageName, lhs.requestorPackageName); + lhs.wifiConfiguration.allowedKeyManagement); } @Override @@ -595,8 +546,6 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc .append(", BSSID Match pattern=").append(bssidPatternMatcher) .append(", SSID=").append(wifiConfiguration.SSID) .append(", BSSID=").append(wifiConfiguration.BSSID) - .append(", requestorUid=").append(requestorUid) - .append(", requestorPackageName=").append(requestorPackageName) .append("]") .toString(); } @@ -618,12 +567,4 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc // not make much sense! return equals(other); } - - /** @hide */ - @Override - public void assertValidFromUid(int requestorUid) { - if (this.requestorUid != requestorUid) { - throw new SecurityException("mismatched UIDs"); - } - } } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index a85f40b3c1b8..b4eb30b8cfe6 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -293,26 +293,34 @@ public class WifiScanner { public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>(); /** * period of background scan; in millisecond, 0 => single shot scan - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int periodInMs; /** * must have a valid REPORT_EVENT value - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int reportEvents; /** * defines number of bssids to cache from each scan - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int numBssidsPerScan; /** * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL * to wake up at fixed interval - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int maxScansToCache; @@ -321,14 +329,18 @@ public class WifiScanner { * a truncated binary exponential backoff bucket and the scan period will grow * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount)) * to maxPeriodInMs - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int maxPeriodInMs; /** * for truncated binary exponential back off bucket, number of scans to perform * for a given period - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int stepCount; @@ -806,7 +818,9 @@ public class WifiScanner { /** * Framework co-ordinates scans across multiple apps; so it may not give exactly the * same period requested. If period of a scan is changed; it is reported by this event. - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public void onPeriodChanged(int periodInMs); @@ -913,7 +927,9 @@ public class WifiScanner { * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @@ -935,7 +951,9 @@ public class WifiScanner { * stop an ongoing wifi scan * @param listener specifies which scan to cancel; must be same object as passed in {@link * #startBackgroundScan} - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @@ -953,7 +971,9 @@ public class WifiScanner { /** * reports currently available scan results on appropriate listeners * @return true if all scan results were reported correctly - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java index c66733472d0e..a4b3e86398a8 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java @@ -143,12 +143,6 @@ public class WifiAwareAgentNetworkSpecifier extends NetworkSpecifier implements } @Override - public void assertValidFromUid(int requestorUid) { - throw new SecurityException( - "WifiAwareAgentNetworkSpecifier should not be used in network requests"); - } - - @Override public NetworkSpecifier redact() { return null; } diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 81bf81e40199..2ebaa1805b2b 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -34,7 +34,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.RemoteException; import android.util.Log; @@ -447,8 +446,7 @@ public class WifiAwareManager { pmk, passphrase, 0, // no port info for deprecated IB APIs - -1, // no transport info for deprecated IB APIs - Process.myUid()); + -1); // no transport info for deprecated IB APIs } /** @hide */ @@ -488,8 +486,7 @@ public class WifiAwareManager { pmk, passphrase, 0, // no port info for OOB APIs - -1, // no transport protocol info for OOB APIs - Process.myUid()); + -1); // no transport protocol info for OOB APIs } private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub { diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java index 5a4ed3c2f5e3..65ac1ab26064 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; import android.text.TextUtils; import java.util.Arrays; @@ -144,19 +143,9 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements */ public final int transportProtocol; - /** - * The UID of the process initializing this network specifier. Validated by receiver using - * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the - * offered network. - * - * @hide - */ - public final int requestorUid; - /** @hide */ public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId, - byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol, - int requestorUid) { + byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol) { this.type = type; this.role = role; this.clientId = clientId; @@ -167,7 +156,6 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements this.passphrase = passphrase; this.port = port; this.transportProtocol = transportProtocol; - this.requestorUid = requestorUid; } public static final @android.annotation.NonNull Creator<WifiAwareNetworkSpecifier> CREATOR = @@ -184,8 +172,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements in.createByteArray(), // pmk in.readString(), // passphrase in.readInt(), // port - in.readInt(), // transportProtocol - in.readInt()); // requestorUid + in.readInt()); // transportProtocol } @Override @@ -221,7 +208,6 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements dest.writeString(passphrase); dest.writeInt(port); dest.writeInt(transportProtocol); - dest.writeInt(requestorUid); } /** @hide */ @@ -238,7 +224,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements @Override public int hashCode() { return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac), - Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid); + Arrays.hashCode(pmk), passphrase, port, transportProtocol); } /** @hide */ @@ -263,8 +249,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements && Arrays.equals(pmk, lhs.pmk) && Objects.equals(passphrase, lhs.passphrase) && port == lhs.port - && transportProtocol == lhs.transportProtocol - && requestorUid == lhs.requestorUid; + && transportProtocol == lhs.transportProtocol; } /** @hide */ @@ -283,19 +268,11 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements // masking PII .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>") .append(", port=").append(port).append(", transportProtocol=") - .append(transportProtocol).append(", requestorUid=").append(requestorUid) + .append(transportProtocol) .append("]"); return sb.toString(); } - /** @hide */ - @Override - public void assertValidFromUid(int requestorUid) { - if (this.requestorUid != requestorUid) { - throw new SecurityException("mismatched UIDs"); - } - } - /** * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a * peer. @@ -463,7 +440,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements return new WifiAwareNetworkSpecifier( WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role, mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId, - null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid()); + null, mPmk, mPskPassphrase, mPort, mTransportProtocol); } } } diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java index 85251e8b1d42..7cc617d61b00 100644 --- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java +++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java @@ -24,7 +24,6 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; /** @@ -34,8 +33,6 @@ import java.util.List; */ @SystemApi public final class NativeScanResult implements Parcelable { - private static final int CAPABILITY_SIZE = 16; - /** @hide */ @VisibleForTesting public byte[] ssid; @@ -56,7 +53,7 @@ public final class NativeScanResult implements Parcelable { public long tsf; /** @hide */ @VisibleForTesting - public BitSet capability; + public int capability; /** @hide */ @VisibleForTesting public boolean associated; @@ -134,7 +131,7 @@ public final class NativeScanResult implements Parcelable { * 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: 8.4.1.4: + * 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 @@ -143,7 +140,7 @@ public final class NativeScanResult implements Parcelable { * Bit 5 - Short Preamble * Bit 6 - PBCC * Bit 7 - Channel Agility - * Bit 8 - Spectrum Mgmt + * Bit 8 - Spectrum Management * Bit 9 - QoS * Bit 10 - Short Slot Time * Bit 11 - APSD @@ -154,7 +151,7 @@ public final class NativeScanResult implements Parcelable { * * @return a bit mask of capabilities. */ - @NonNull public BitSet getCapabilities() { + @NonNull public int getCapabilities() { return capability; } @@ -188,13 +185,7 @@ public final class NativeScanResult implements Parcelable { out.writeInt(frequency); out.writeInt(signalMbm); out.writeLong(tsf); - int capabilityInt = 0; - for (int i = 0; i < CAPABILITY_SIZE; i++) { - if (capability.get(i)) { - capabilityInt |= 1 << i; - } - } - out.writeInt(capabilityInt); + out.writeInt(capability); out.writeInt(associated ? 1 : 0); out.writeTypedList(radioChainInfos); } @@ -220,13 +211,7 @@ public final class NativeScanResult implements Parcelable { result.frequency = in.readInt(); result.signalMbm = in.readInt(); result.tsf = in.readLong(); - int capabilityInt = in.readInt(); - result.capability = new BitSet(CAPABILITY_SIZE); - for (int i = 0; i < CAPABILITY_SIZE; i++) { - if ((capabilityInt & (1 << i)) != 0) { - result.capability.set(i); - } - } + result.capability = in.readInt(); result.associated = (in.readInt() != 0); result.radioChainInfos = new ArrayList<>(); in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR); diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java index 57c9ca5fd302..533d37d3a23a 100644 --- a/wifi/java/android/net/wifi/wificond/PnoSettings.java +++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java @@ -16,6 +16,7 @@ package android.net.wifi.wificond; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -33,7 +34,7 @@ import java.util.Objects; */ @SystemApi public final class PnoSettings implements Parcelable { - private int mIntervalMs; + private long mIntervalMs; private int mMin2gRssi; private int mMin5gRssi; private int mMin6gRssi; @@ -47,17 +48,17 @@ public final class PnoSettings implements Parcelable { * * @return An interval in milliseconds. */ - public int getIntervalMillis() { + public @DurationMillisLong long getIntervalMillis() { return mIntervalMs; } /** * Set the requested PNO scan interval in milliseconds. * - * @param intervalMs An interval in milliseconds. + * @param intervalMillis An interval in milliseconds. */ - public void setIntervalMillis(int intervalMs) { - this.mIntervalMs = intervalMs; + public void setIntervalMillis(@DurationMillisLong long intervalMillis) { + this.mIntervalMs = intervalMillis; } /** @@ -176,7 +177,7 @@ public final class PnoSettings implements Parcelable { **/ @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(mIntervalMs); + out.writeLong(mIntervalMs); out.writeInt(mMin2gRssi); out.writeInt(mMin5gRssi); out.writeInt(mMin6gRssi); @@ -189,7 +190,7 @@ public final class PnoSettings implements Parcelable { @Override public PnoSettings createFromParcel(Parcel in) { PnoSettings result = new PnoSettings(); - result.mIntervalMs = in.readInt(); + result.mIntervalMs = in.readLong(); result.mMin2gRssi = in.readInt(); result.mMin5gRssi = in.readInt(); result.mMin6gRssi = in.readInt(); diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java index 43aa1b64efbc..7a31a5afab05 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java @@ -496,22 +496,17 @@ public class WifiCondManager { } /** - * Initializes WifiCondManager & registers a death notification for the WifiCondManager which - * acts as a proxy for the wificond daemon (i.e. the death listener will be called when and if - * the wificond daemon dies). - * - * Note: This method clears any existing state in wificond daemon. + * Register a death notification for the WifiCondManager which acts as a proxy for the + * wificond daemon (i.e. the death listener will be called when and if the wificond daemon + * dies). * * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies. - * @return Returns true on success. */ - public boolean initialize(@NonNull Runnable deathEventHandler) { + public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) { if (mDeathEventHandler != null) { Log.e(TAG, "Death handler already present"); } mDeathEventHandler = deathEventHandler; - tearDownInterfaces(); - return true; } /** @@ -603,11 +598,12 @@ public class WifiCondManager { } /** - * Tear down a specific client (STA) interface, initially configured using + * Tear down a specific client (STA) interface configured using * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. * * @param ifaceName Name of the interface to tear down. - * @return Returns true on success. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). */ public boolean tearDownClientInterface(@NonNull String ifaceName) { if (getClientInterface(ifaceName) == null) { @@ -681,11 +677,12 @@ public class WifiCondManager { } /** - * Tear down a Soft AP interface initially configured using + * Tear down a Soft AP interface configured using * {@link #setupInterfaceForSoftApMode(String)}. * * @param ifaceName Name of the interface to tear down. - * @return Returns true on success. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). */ public boolean tearDownSoftApInterface(@NonNull String ifaceName) { if (getApInterface(ifaceName) == null) { @@ -750,9 +747,13 @@ public class WifiCondManager { /** * Request signal polling. * - * @param ifaceName Name of the interface on which to poll. + * @param ifaceName Name of the interface on which to poll. The interface must have been + * already set up using + *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @return A {@link SignalPollResult} object containing interface statistics, or a null on - * error. + * error (e.g. the interface hasn't been set up yet). */ @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) { IClientInterface iface = getClientInterface(ifaceName); @@ -776,10 +777,14 @@ public class WifiCondManager { } /** - * Get current transmit (Tx) packet counters of the specified interface. + * Get current transmit (Tx) packet counters of the specified interface. The interface must + * have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. * * @param ifaceName Name of the interface. - * @return {@link TxPacketCounters} of the current interface or null on error. + * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when + * called before the interface has been set up). */ @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) { IClientInterface iface = getClientInterface(ifaceName); @@ -813,10 +818,15 @@ public class WifiCondManager { * be done using {@link #startScan(String, int, Set, List)} or * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface. * @param scanType The type of scan result to be returned, can be * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}. - * @return Returns an array of {@link NativeScanResult} or an empty array on failure. + * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when + * called before the interface has been set up). */ @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName, @ScanResultType int scanType) { @@ -869,13 +879,19 @@ public class WifiCondManager { * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which to initiate the scan. * @param scanType Type of scan to perform, can be any of * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}. * @param freqs list of frequencies to scan for, if null scan all supported channels. - * @param hiddenNetworkSSIDs List of hidden networks to be scanned for. - * @return Returns true on success. + * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that + * no hidden frequencies will be scanned for. + * @return Returns true on success, false on failure (e.g. when called before the interface + * has been set up). */ public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) { @@ -931,11 +947,16 @@ public class WifiCondManager { * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which to request a PNO. * @param pnoSettings PNO scan configuration. * @param executor The Executor on which to execute the callback. * @param callback Callback for the results of the offload request. - * @return true on success. + * @return true on success, false on failure (e.g. when called before the interface has been set + * up). */ public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings, @NonNull @CallbackExecutor Executor executor, @@ -969,8 +990,13 @@ public class WifiCondManager { * Stop PNO scan configured with * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which the PNO scan was configured. - * @return true on success. + * @return true on success, false on failure (e.g. when called before the interface has been + * set up). */ public boolean stopPnoScan(@NonNull String ifaceName) { IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); @@ -987,7 +1013,13 @@ public class WifiCondManager { } /** - * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. + * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure + * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then + * this method has no impact. * * @param ifaceName Name of the interface on which the scan was started. */ @@ -1055,7 +1087,14 @@ public class WifiCondManager { } /** - * Get the device phy capabilities for a given interface + * Get the device phy capabilities for a given interface. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has + * not been set up). */ @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { if (mWificond == null) { @@ -1071,13 +1110,19 @@ public class WifiCondManager { } /** - * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is - * configured using {@link #setupInterfaceForSoftApMode(String)}. + * Register the provided callback handler for SoftAp events. The interface must first be created + * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until + * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration + * method is provided). + * <p> + * Note that only one callback can be registered at a time - any registration overrides previous + * registrations. * * @param ifaceName Name of the interface on which to register the callback. * @param executor The Executor on which to execute the callbacks. * @param callback Callback for AP events. - * @return true on success, false otherwise. + * @return true on success, false on failure (e.g. when called on an interface which has not + * been set up). */ public boolean registerApCallback(@NonNull String ifaceName, @NonNull @CallbackExecutor Executor executor, @@ -1113,6 +1158,10 @@ public class WifiCondManager { * Send a management frame on the specified interface at the specified rate. Useful for probing * the link with arbitrary frames. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName The interface on which to send the frame. * @param frame The raw byte array of the management frame to tramit. * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java deleted file mode 100644 index 060c85cac209..000000000000 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ /dev/null @@ -1,641 +0,0 @@ -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License") { - * throw new UnsupportedOperationException(); - } - * 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.wifi; - -import android.content.pm.ParceledListSlice; -import android.net.DhcpInfo; -import android.net.Network; -import android.net.wifi.IActionListener; -import android.net.wifi.IDppCallback; -import android.net.wifi.ILocalOnlyHotspotCallback; -import android.net.wifi.INetworkRequestMatchCallback; -import android.net.wifi.IOnWifiActivityEnergyInfoListener; -import android.net.wifi.IOnWifiUsabilityStatsListener; -import android.net.wifi.IScanResultsCallback; -import android.net.wifi.ISoftApCallback; -import android.net.wifi.ISuggestionConnectionStatusListener; -import android.net.wifi.ITrafficStateCallback; -import android.net.wifi.ITxPacketCountListener; -import android.net.wifi.IWifiConnectedNetworkScorer; -import android.net.wifi.IWifiManager; -import android.net.wifi.ScanResult; -import android.net.wifi.SoftApConfiguration; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiNetworkSuggestion; -import android.net.wifi.hotspot2.IProvisioningCallback; -import android.net.wifi.hotspot2.OsuProvider; -import android.net.wifi.hotspot2.PasspointConfiguration; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.WorkSource; -import android.os.connectivity.WifiActivityEnergyInfo; - -import java.util.List; -import java.util.Map; - -/** - * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions. - * - * This class is meant to be extended by real implementations of IWifiManager in order to facilitate - * cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of - * deprecated APIs, or the migration of existing API signatures. - * - * When an existing API is scheduled for removal, it can be removed from IWifiManager.aidl - * immediately and marked as @Deprecated first in this class. Children inheriting this class are - * then given a short grace period to update themselves before the @Deprecated stub is removed for - * good. If the API scheduled for removal has a replacement or an overload (signature change), - * these should be introduced before the stub is removed to allow children to migrate. - * - * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as - * well otherwise compilation will fail. - */ -public class BaseWifiService extends IWifiManager.Stub { - - private static final String TAG = BaseWifiService.class.getSimpleName(); - - @Override - public long getSupportedFeatures() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */ - @Deprecated - public WifiActivityEnergyInfo reportActivityInfo() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */ - @Deprecated - public void requestActivityInfo(ResultReceiver result) { - throw new UnsupportedOperationException(); - } - - @Override - public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public ParceledListSlice getConfiguredNetworks(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( - List<OsuProvider> osuProviders) { - throw new UnsupportedOperationException(); - } - - @Override - public int addOrUpdateNetwork(WifiConfiguration config, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addOrUpdatePasspointConfiguration( - PasspointConfiguration config, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removePasspointConfiguration(String fqdn, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<PasspointConfiguration> getPasspointConfigurations(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> fqdnList) { - throw new UnsupportedOperationException(); - } - - @Override - public void queryPasspointIcon(long bssid, String fileName) { - throw new UnsupportedOperationException(); - } - - @Override - public int matchProviderWithCurrentNetwork(String fqdn) { - throw new UnsupportedOperationException(); - } - - @Override - public void deauthenticateNetwork(long holdoff, boolean ess) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeNetwork(int netId, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean enableNetwork(int netId, boolean disableOthers, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean disableNetwork(int netId, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoinGlobal(boolean choice) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoin(int netId, boolean choice) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) { - throw new UnsupportedOperationException(); - } - - @Override - public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public void setMeteredOverridePasspoint(String fqdn, int meteredOverride) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startScan(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public List<ScanResult> getScanResults(String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean disconnect(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean reconnect(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean reassociate(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiEnabled(String packageName, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public int getWifiEnabledState() { - throw new UnsupportedOperationException(); - } - - @Override - public String getCountryCode() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #is5GHzBandSupported} instead */ - @Deprecated - public boolean isDualBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean is5GHzBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean is6GHzBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isWifiStandardSupported(int standard) { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link WifiManager#isStaApConcurrencySupported()} */ - @Deprecated - public boolean needs5GHzToAnyApBandConversion() { - throw new UnsupportedOperationException(); - } - - @Override - public DhcpInfo getDhcpInfo() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isScanAlwaysAvailable() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean acquireWifiLock(IBinder lock, int lockType, String tag, WorkSource ws) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean releaseWifiLock(IBinder lock) { - throw new UnsupportedOperationException(); - } - - @Override - public void initializeMulticastFiltering() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isMulticastEnabled() { - throw new UnsupportedOperationException(); - } - - @Override - public void acquireMulticastLock(IBinder binder, String tag) { - throw new UnsupportedOperationException(); - } - - @Override - public void releaseMulticastLock(String tag) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateInterfaceIpState(String ifaceName, int mode) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startSoftAp(WifiConfiguration wifiConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startTetheredHotspot(SoftApConfiguration softApConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean stopSoftAp() { - throw new UnsupportedOperationException(); - } - - @Override - public int startLocalOnlyHotspot(ILocalOnlyHotspotCallback callback, String packageName, - String featureId, SoftApConfiguration customConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopLocalOnlyHotspot() { - throw new UnsupportedOperationException(); - } - - @Override - public void startWatchLocalOnlyHotspot(ILocalOnlyHotspotCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopWatchLocalOnlyHotspot() { - throw new UnsupportedOperationException(); - } - - @Override - public int getWifiApEnabledState() { - throw new UnsupportedOperationException(); - } - - @Override - public WifiConfiguration getWifiApConfiguration() { - throw new UnsupportedOperationException(); - } - - @Override - public SoftApConfiguration getSoftApConfiguration() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setSoftApConfiguration(SoftApConfiguration softApConfig, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void notifyUserOfApBandConversion(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void enableTdls(String remoteIPAddress, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public String getCurrentNetworkWpsNfcConfigurationToken() { - throw new UnsupportedOperationException(); - } - - @Override - public void enableVerboseLogging(int verbose) { - throw new UnsupportedOperationException(); - } - - @Override - public int getVerboseLoggingLevel() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */ - @Deprecated - public void enableWifiConnectivityManager(boolean enabled) { - throw new UnsupportedOperationException(); - } - - @Override - public void disableEphemeralNetwork(String SSID, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void factoryReset(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public Network getCurrentNetwork() { - throw new UnsupportedOperationException(); - } - - @Override - public byte[] retrieveBackupData() { - throw new UnsupportedOperationException(); - } - - @Override - public void restoreBackupData(byte[] data) { - throw new UnsupportedOperationException(); - } - - @Override - public byte[] retrieveSoftApBackupData() { - throw new UnsupportedOperationException(); - } - - @Override - public SoftApConfiguration restoreSoftApBackupData(byte[] data) { - throw new UnsupportedOperationException(); - } - - @Override - public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) { - throw new UnsupportedOperationException(); - } - - @Override - public void startSubscriptionProvisioning( - OsuProvider provider, IProvisioningCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerSoftApCallback( - IBinder binder, ISoftApCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterSoftApCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerTrafficStateCallback( - IBinder binder, ITrafficStateCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterTrafficStateCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerNetworkRequestMatchCallback( - IBinder binder, INetworkRequestMatchCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public int addNetworkSuggestions( - List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName, - String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public int removeNetworkSuggestions( - List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiNetworkSuggestion> getNetworkSuggestions(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public String[] getFactoryMacAddresses() { - throw new UnsupportedOperationException(); - } - - @Override - public void setDeviceMobilityState(int state) { - throw new UnsupportedOperationException(); - } - - @Override - public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri, - int selectedNetworkId, int netRole, IDppCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void startDppAsEnrolleeInitiator(IBinder binder, String configuratorUri, - IDppCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopDppSession() throws RemoteException { - throw new UnsupportedOperationException(); - } - - @Override - public void addOnWifiUsabilityStatsListener( - IBinder binder, IOnWifiUsabilityStatsListener listener, int listenerIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeOnWifiUsabilityStatsListener(int listenerIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { - throw new UnsupportedOperationException(); - } - - @Override - public void connect(WifiConfiguration config, int netId, IBinder binder, - IActionListener callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void save(WifiConfiguration config, IBinder binder, IActionListener callback, - int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void forget(int netId, IBinder binder, IActionListener callback, - int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void getTxPacketCount(String packageName, IBinder binder, - ITxPacketCountListener callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerScanResultsCallback(IScanResultsCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterScanResultsCallback(IScanResultsCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerSuggestionConnectionStatusListener(IBinder binder, - ISuggestionConnectionStatusListener listener, - int listenerIdentifier, String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, - String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public int calculateSignalLevel(int rssi) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiConnectedNetworkScorer(IBinder binder, - IWifiConnectedNetworkScorer scorer) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearWifiConnectedNetworkScorer() { - throw new UnsupportedOperationException(); - } - - @Override - public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( - List<WifiNetworkSuggestion> networkSuggestions, - List<ScanResult> scanResults, - String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } -} diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 8023160a811e..05a3dce44022 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -336,6 +336,20 @@ public class WifiConfigurationTest { } /** + * Ensure that {@link NetworkSelectionStatus#getMaxNetworkSelectionDisableReason()} returns + * the maximum disable reason. + */ + @Test + public void testNetworkSelectionGetMaxNetworkSelectionDisableReason() { + int maxReason = Integer.MIN_VALUE; + for (int i = 0; i < NetworkSelectionStatus.DISABLE_REASON_INFOS.size(); i++) { + int reason = NetworkSelectionStatus.DISABLE_REASON_INFOS.keyAt(i); + maxReason = Math.max(maxReason, reason); + } + assertEquals(maxReason, NetworkSelectionStatus.getMaxNetworkSelectionDisableReason()); + } + + /** * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the * {@link WifiConfiguration} object correctly for SAE security type. * @throws Exception diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index a189d507a32a..6320f85eb974 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1867,7 +1867,7 @@ public class WifiManagerTest { * Tests that passing a null Executor to {@link WifiManager#getWifiActivityEnergyInfoAsync} * throws an exception. */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testGetWifiActivityInfoNullExecutor() throws Exception { mWifiManager.getWifiActivityEnergyInfoAsync(null, mOnWifiActivityEnergyInfoListener); } @@ -1876,7 +1876,7 @@ public class WifiManagerTest { * Tests that passing a null listener to {@link WifiManager#getWifiActivityEnergyInfoAsync} * throws an exception. */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testGetWifiActivityInfoNullListener() throws Exception { mWifiManager.getWifiActivityEnergyInfoAsync(mExecutor, null); } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java index adc41f0df4b4..0233ee2e2785 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue; import android.net.MacAddress; import android.net.MatchAllNetworkSpecifier; -import android.net.NetworkRequest; import android.os.Parcel; import android.os.PatternMatcher; import android.util.Pair; @@ -36,10 +35,6 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkAgentSpecifierTest { - private static final int TEST_UID = 5; - private static final int TEST_UID_1 = 8; - private static final String TEST_PACKAGE = "com.test"; - private static final String TEST_PACKAGE_1 = "com.test.1"; private static final String TEST_SSID = "Test123"; private static final String TEST_SSID_PATTERN = "Test"; private static final String TEST_SSID_1 = "456test"; @@ -71,16 +66,6 @@ public class WifiNetworkAgentSpecifierTest { } /** - * Validate that the NetworkAgentSpecifier cannot be used in a {@link NetworkRequest} by apps. - */ - @Test(expected = IllegalStateException.class) - public void testWifiNetworkAgentSpecifierNotUsedInNetworkRequest() { - WifiNetworkAgentSpecifier specifier = createDefaultNetworkAgentSpecifier(); - - specifier.assertValidFromUid(TEST_UID); - } - - /** * Validate NetworkAgentSpecifier equals with itself. * a) Create network agent specifier 1 for WPA_PSK network * b) Create network agent specifier 2 with the same params as specifier 1. @@ -105,15 +90,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -129,15 +112,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.SSID = TEST_SSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -153,15 +134,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -215,8 +194,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -244,8 +222,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -273,8 +250,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -293,8 +269,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID_1 + "\""; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -306,8 +281,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -326,8 +300,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB); @@ -340,8 +313,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -360,8 +332,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -374,8 +345,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -402,41 +372,12 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); } - /** - * Validate {@link WifiNetworkAgentSpecifier} with {@link WifiNetworkSpecifier} matching. - * a) Create network agent specifier for WPA_PSK network - * b) Create network specifier with matching SSID and BSSID pattern, but different UID. - * c) Ensure that the agent specifier is not satisfied by specifier. - */ - @Test - public void - testWifiNetworkAgentSpecifierDoesNotSatisfyNetworkSpecifierWithDifferentUid() { - WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = createDefaultNetworkAgentSpecifier(); - - PatternMatcher ssidPattern = - new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); - Pair<MacAddress, MacAddress> bssidPattern = - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)); - WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration(); - wificonfigurationNetworkSpecifier.allowedKeyManagement - .set(WifiConfiguration.KeyMgmt.WPA_PSK); - WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( - ssidPattern, - bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID_1, TEST_PACKAGE_1); - - assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); - assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); - } private WifiConfiguration createDefaultWifiConfiguration() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); @@ -448,8 +389,7 @@ public class WifiNetworkAgentSpecifierTest { } private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() { - return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID, - TEST_PACKAGE); + return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration()); } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java index 16197443b9d9..3b6723613c50 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java @@ -29,7 +29,6 @@ import android.net.MatchAllNetworkSpecifier; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.PatternMatcher; -import android.os.Process; import android.util.Pair; import androidx.test.filters.SmallTest; @@ -41,8 +40,6 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkSpecifierTest { - private static final int TEST_UID = 5; - private static final String TEST_PACKAGE_NAME = "com.test"; private static final String TEST_SSID = "Test123"; private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00"; private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00"; @@ -62,7 +59,6 @@ public class WifiNetworkSpecifierTest { assertTrue(specifier instanceof WifiNetworkSpecifier); WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier; - assertEquals(Process.myUid(), wifiNetworkSpecifier.requestorUid); assertEquals(TEST_SSID, wifiNetworkSpecifier.ssidPatternMatcher.getPath()); assertEquals(PATTERN_PREFIX, wifiNetworkSpecifier.ssidPatternMatcher.getType()); assertEquals(WifiManager.ALL_ZEROS_MAC_ADDRESS, @@ -367,8 +363,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); Parcel parcelW = Parcel.obtain(); specifier.writeToParcel(parcelW, 0); @@ -399,8 +394,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertTrue(specifier.satisfiedBy(null)); assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier())); @@ -422,15 +416,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertTrue(specifier2.satisfiedBy(specifier1)); } @@ -451,8 +443,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration1, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); @@ -460,8 +451,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration2, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration2); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -482,15 +472,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher("", PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -511,44 +499,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); - - assertFalse(specifier2.satisfiedBy(specifier1)); - } - - /** - * Validate NetworkSpecifier matching. - * a) Create network specifier 1 for WPA_PSK network - * b) Create network specifier 2 with different package name . - * c) Ensure that the specifier 2 is not satisfied by specifier 1. - */ - @Test - public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() { - WifiConfiguration wifiConfiguration = new WifiConfiguration(); - wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); - wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY; - - WifiNetworkSpecifier specifier1 = - new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); - - WifiNetworkSpecifier specifier2 = - new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME + "blah"); + wifiConfiguration); assertFalse(specifier2.satisfiedBy(specifier1)); } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java index c3b62854f12c..81b02fa5f801 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java @@ -162,17 +162,6 @@ public class WifiAwareAgentNetworkSpecifierTest { collector.checkThat("Match unexpected", oldNs.satisfiedBy(newNs), equalTo(false)); } - /** - * Validate that agent network specifier cannot be used as in network requests - i.e. that - * throws an exception when queried for UID validity. - */ - @Test(expected = SecurityException.class) - public void testNoUsageInRequest() { - WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier(); - - dut.assertValidFromUid(0); - } - // utilities /** @@ -182,6 +171,6 @@ public class WifiAwareAgentNetworkSpecifierTest { WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) { return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB, WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6], - null, null, 10, 5, 0); + null, null, 10, 5); } } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 65fbf5b099d4..c5f98045082b 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -1564,7 +1564,7 @@ public class WifiAwareManagerTest { WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB, WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334, HexEncoding.decode("000102030405".toCharArray(), false), - "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001); + "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4); Parcel parcelW = Parcel.obtain(); ns.writeToParcel(parcelW, 0); diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java index 06f12f7f37ea..0df170f8786c 100644 --- a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java @@ -28,7 +28,6 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; /** * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}. @@ -46,7 +45,7 @@ public class NativeScanResultTest { private static final int TEST_FREQUENCY = 2456; private static final int TEST_SIGNAL_MBM = -45; private static final long TEST_TSF = 34455441; - private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }}; + private static final int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5); private static final boolean TEST_ASSOCIATED = true; private static final int[] RADIO_CHAIN_IDS = { 0, 1 }; private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 }; diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java index 5ba02a779663..32105be6ae4c 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java @@ -720,8 +720,7 @@ public class WifiCondManagerTest { @Test public void testRegisterDeathHandler() throws Exception { Runnable deathHandler = mock(Runnable.class); - assertTrue(mWificondControl.initialize(deathHandler)); - verify(mWificond).tearDownInterfaces(); + mWificondControl.setOnServiceDeadCallback(deathHandler); mWificondControl.binderDied(); mLooper.dispatchAll(); verify(deathHandler).run(); @@ -734,7 +733,7 @@ public class WifiCondManagerTest { @Test public void testDeathHandling() throws Exception { Runnable deathHandler = mock(Runnable.class); - assertTrue(mWificondControl.initialize(deathHandler)); + mWificondControl.setOnServiceDeadCallback(deathHandler); testSetupInterfaceForClientMode(); |