diff options
343 files changed, 8976 insertions, 3955 deletions
diff --git a/Android.bp b/Android.bp index 5796fb17046c..12bc90680dd5 100644 --- a/Android.bp +++ b/Android.bp @@ -653,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: [ @@ -1010,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..a7b69c420c59 --- /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); + // get and delete blobId + } + } 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/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/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/api/current.txt b/api/current.txt index 87a5cd7a648b..81c4c922b730 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3850,7 +3850,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(); @@ -26799,7 +26799,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"; } @@ -28772,6 +28771,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"; @@ -28797,6 +28797,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"; @@ -28890,6 +28891,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"; @@ -28937,6 +28939,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"; @@ -28953,6 +28957,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"; @@ -29018,6 +29023,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"; @@ -29078,6 +29084,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"; @@ -35680,6 +35687,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; diff --git a/api/system-current.txt b/api/system-current.txt index b22950687fe2..7903fbab696a 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"; @@ -692,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; @@ -700,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"; @@ -1472,9 +1473,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(); @@ -2098,6 +2099,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 @@ -2201,6 +2206,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 @@ -2284,7 +2291,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 { @@ -8195,7 +8202,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(); @@ -8231,12 +8238,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); @@ -8261,10 +8268,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); diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index a57e178879fa..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` 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 2237bf2b2acb..3a2472efb601 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", @@ -110,7 +110,7 @@ cc_defaults { ], cflags: [ - // "-DNEW_ENCODING_SCHEME", + "-DNEW_ENCODING_SCHEME", ], local_include_dirs: [ @@ -121,19 +121,18 @@ cc_defaults { "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 +159,7 @@ genrule { ], } -cc_library_shared { +cc_library_static { name: "libstatsmetadata", host_supported: true, generated_sources: [ @@ -277,8 +276,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/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..0256e3617dd7 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, diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 3bfaa9842950..af3016f86773 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -193,16 +193,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( diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 9808f04e0a0d..183d7411489c 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3709,6 +3709,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; 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 3e8c7357b4ca..e8fc603088a1 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -837,6 +837,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..e4b784e069ca 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -144,6 +144,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 +215,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/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/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/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/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/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 0133a4472686..c0e8957727cf 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java @@ -50,17 +50,17 @@ import java.util.Objects; * * @hide */ -public final class PhoneTimeSuggestion implements Parcelable { +public final class TelephonyTimeSuggestion implements Parcelable { /** @hide */ - 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]; } }; @@ -68,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") @@ -102,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; @@ -111,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() { @@ -121,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() { @@ -132,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) { @@ -144,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) { @@ -161,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); } @@ -173,7 +173,7 @@ public final class PhoneTimeSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeSuggestion{" + return "TelephonyTimeSuggestion{" + "mSlotIndex='" + mSlotIndex + '\'' + ", mUtcTime=" + mUtcTime + ", mDebugInfo=" + mDebugInfo @@ -181,7 +181,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeSuggestion} instances. + * Builds {@link TelephonyTimeSuggestion} instances. * * @hide */ @@ -193,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; @@ -202,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) { @@ -218,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) { @@ -229,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 df4f513ab095..84ad495da09b 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -45,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 9147b4462492..150c01d59899 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java @@ -56,18 +56,18 @@ import java.util.Objects; * * @hide */ -public final class PhoneTimeZoneSuggestion implements Parcelable { +public final class TelephonyTimeZoneSuggestion implements Parcelable { /** @hide */ @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]; } }; @@ -76,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(); } @@ -144,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; @@ -153,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); } @@ -185,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; @@ -195,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() { @@ -206,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() { @@ -216,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() { @@ -226,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() { @@ -237,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) { @@ -249,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) { @@ -266,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 @@ -280,7 +281,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeZoneSuggestion{" + return "TelephonyTimeZoneSuggestion{" + "mSlotIndex=" + mSlotIndex + ", mZoneId='" + mZoneId + '\'' + ", mMatchType=" + mMatchType @@ -290,7 +291,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeZoneSuggestion} instances. + * Builds {@link TelephonyTimeZoneSuggestion} instances. * * @hide */ @@ -304,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; @@ -313,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) { @@ -324,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) { @@ -335,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) { @@ -346,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) { @@ -384,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 6a3953eccb2d..20761ad2d447 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -47,8 +47,8 @@ public interface TimeZoneDetector { * * @hide */ - @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/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/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 51d5c3f6725b..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); } } 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/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/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 8990bdf06a6f..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; @@ -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); } /** 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/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 848868a5532f..3ff6f549e337 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -41,6 +41,7 @@ import android.util.Slog; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassificationSessionId; @@ -404,22 +405,27 @@ public abstract class TextClassifierService extends Service { */ @NonNull public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) { - 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."); - } 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 **/ diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java index ab2c4fc1bf23..ee3a8a79d5d7 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.icu.util.TimeZone; import java.util.ArrayList; @@ -206,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/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/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/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/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/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/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/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_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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7a302566a872..32a6cc3b5344 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2605,7 +2605,7 @@ <p>Not for use by third-party applications. @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|privileged + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" + android:protectionLevel="signature|privileged" /> + <!-- 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" /> @@ -5302,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 b2d08a9730fc..31e68e88c0a8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4343,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/symbols.xml b/core/res/res/values/symbols.xml index c59d25f10b0b..2453bb18577f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3863,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/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/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..7733559c156a 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.getIsHashedValue()).isTrue(); + assertThat(stringAtomicFormula.getValue()).matches(appCert); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test - public void testValidAtomicFormula_stringValue_installerCertificateAutoHashed() { + public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() { String installerCert = "cert"; StringAtomicFormula stringAtomicFormula = new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, @@ -109,8 +109,8 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo( AtomicFormula.INSTALLER_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCert); - assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); + assertThat(stringAtomicFormula.getValue()).matches(installerCert); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index c1806028f75b..dc03167f51a9 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()).isFalse(); } @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()).isFalse(); } @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/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/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/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c1435d1ea2d5..aa8bac9362ab 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -174,7 +174,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++; @@ -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/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/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/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 194669c19018..9131f3bc960d 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -37,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 d0b5d486d19c..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 @@ -520,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 515cabe5a5c9..d7b74df0a5e6 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -462,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/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/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 f1a08f29e0cc..1a866cafff90 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java @@ -154,20 +154,6 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onUpdateVolume(String routeId, int 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/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index de523d9f9bc8..213e3657254d 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,8 +29,11 @@ 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 java.text.NumberFormat; @@ -424,6 +429,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/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/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/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/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 07a926fa75d3..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> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index b8d32aec30e3..5c65977c8929 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -583,11 +583,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/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..082b0656b32e 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1899,9 +1899,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/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/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..61915ad92d87 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,11 @@ 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.collection.notifcollection.CommonNotifCollection; +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 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 +98,7 @@ import dagger.Lazy; */ @Singleton public class NotificationEntryManager implements + CommonNotifCollection, Dumpable, InflationCallback, VisualStabilityManager.Callback { @@ -130,6 +133,7 @@ public class NotificationEntryManager implements 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; @@ -318,8 +322,7 @@ 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. @@ -328,7 +331,7 @@ public class NotificationEntryManager implements if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { mNotifLog.log(NotifEvent.INFLATED, entry); - listener.onEntryInflated(entry, inflatedFlags); + listener.onEntryInflated(entry); } addActiveNotification(entry); updateNotifications("onAsyncInflationFinished"); @@ -488,6 +491,13 @@ public class NotificationEntryManager implements 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 +563,10 @@ public class NotificationEntryManager implements mLeakDetector.trackInstance(entry); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -566,6 +580,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPendingEntryAdded(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryAdded(entry); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -600,6 +617,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryUpdated(entry); + } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -674,6 +694,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) { @@ -862,6 +885,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/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/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 2a7683a8c7c2..59d82a1bc5cf 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,30 @@ 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(); + 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/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/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..8280a63dedd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -0,0 +1,157 @@ +/* + * 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; + } + + /** + * 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/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..486aac894d9b 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,14 @@ public class AuthContainerViewTest extends SysuiTestCase { assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType); } + @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/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..b51581f544f5 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 @@ -84,9 +84,10 @@ 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 +100,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; @@ -206,20 +208,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), @@ -269,7 +271,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 +311,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -331,6 +337,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification_prePostEntryOrder() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -399,7 +406,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..a9f9db67ff0b 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 @@ -48,8 +48,8 @@ class TestableNotificationEntryManager( 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/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/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..66aa5e18d0c9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -0,0 +1,247 @@ +/* + * 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 testSetShouldContentViewsBeBound_bindsContent() { + // 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 testSetShouldContentViewsBeBound_unbindsContent() { + // 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 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/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/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/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ec3dbe9f850e..672617090a71 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6581,6 +6581,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<>(); @@ -6591,7 +6592,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; @@ -6641,7 +6645,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. diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 50843b0999a1..0ab8af6ad027 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -305,11 +305,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 +388,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); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index f2dce06698d5..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. @@ -552,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(); } } @@ -582,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..12b1cbf877a3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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; } 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/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 2fc9d04623f8..c4e6427a0dfb 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -16,6 +16,11 @@ 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.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -67,12 +72,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 +89,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); @@ -99,6 +108,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + checkCompatChangeReadAndLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return true; @@ -108,6 +118,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 +151,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 +159,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 +173,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 +188,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 +233,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 +291,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/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 704cbff44388..fc9542a33a78 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -65,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; @@ -133,14 +134,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { hdrCapabilities, isInternal); mDevices.put(physicalDisplayId, device); sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); - } else { - boolean changed = device.updateDisplayConfigsLocked(configs, activeConfig, - configSpecs); - changed |= device.updateColorModesLocked(colorModes, activeColorMode); - changed |= device.updateHdrCapabilitiesLocked(hdrCapabilities); - if (changed) { - sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); - } + } else if (device.updateDisplayProperties(configs, activeConfig, + configSpecs, colorModes, activeColorMode, hdrCapabilities)) { + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); } } else { // The display is no longer available. Ignore the attempt to add it. @@ -215,10 +211,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { mPhysicalDisplayId = physicalDisplayId; mIsInternal = isInternal; mDisplayInfo = info; - - updateDisplayConfigsLocked(configs, activeConfigId, configSpecs); - updateColorModesLocked(colorModes, activeColorMode); - updateHdrCapabilitiesLocked(hdrCapabilities); + updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes, + activeColorMode, hdrCapabilities); mSidekickInternal = LocalServices.getService(SidekickInternal.class); if (mIsInternal) { LightsManager lights = LocalServices.getService(LightsManager.class); @@ -240,13 +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) { 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]; @@ -286,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(); @@ -294,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 @@ -323,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."); @@ -345,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."); } @@ -797,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); @@ -922,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/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 7b17b4ed6219..83588846ade8 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -63,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 e22ea4681428..d08fb71d3dee 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -138,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); @@ -518,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 358cc29e3d64..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(); @@ -628,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(); @@ -713,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); @@ -1459,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(); 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/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 798a781a9d6c..888f7ce74ba4 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -153,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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b52289e36b50..f07113591fa5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2681,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); @@ -2730,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 { 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/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 8e5ff66a7561..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; @@ -1658,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) @@ -1668,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 @@ -1995,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; @@ -2096,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*/, @@ -13978,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; @@ -13989,6 +13997,7 @@ public class PackageManagerService extends IPackageManager.Stub this.appId = appId; this.seinfo = seinfo; this.targetSdkVersion = targetSdkVersion; + this.fromCodePath = fromCodePath; } } @@ -14114,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; @@ -14136,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 @@ -14772,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 @@ -14785,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; @@ -14803,6 +14816,7 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; this.forceQueryableOverride = forceQueryableOverride; this.mMultiPackageInstallParams = multiPackageInstallParams; + this.mDataLoaderType = dataLoaderType; } /** New install */ @@ -14812,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(); @@ -14903,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; } @@ -14983,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()); } @@ -15104,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; @@ -22054,6 +22069,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageFreezer freezer; final int[] installedUserIds; final boolean isCurrentLocationExternal; + final String fromCodePath; // reader synchronized (mLock) { @@ -22110,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(); @@ -22238,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; } @@ -22251,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; @@ -22757,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 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/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/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/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 0bb0f94d1243..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; @@ -94,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 @@ -131,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 a7c3b4dad552..a5fba4e6ba49 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -20,7 +20,7 @@ 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; @@ -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 19435ee16660..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 {@link 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/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 381ee101e125..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; @@ -101,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 @@ -122,10 +122,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged); } - private void enforceSuggestPhoneTimeZonePermission() { + private void enforceSuggestTelephonyTimeZonePermission() { 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 enforceSuggestManualTimeZonePermission() { diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 1d439e93a1f7..0eb27cc5cff0 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -17,7 +17,7 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import java.io.PrintWriter; @@ -38,13 +38,13 @@ public interface TimeZoneDetectorStrategy { /** * 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. */ - void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion); + void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); /** * Called when there has been a change to the automatic time zone detection setting. diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index f85f9fe998a5..652dbe153680 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -15,17 +15,17 @@ */ 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 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.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; import android.util.LocalLog; import android.util.Slog; @@ -44,11 +44,11 @@ import java.util.Objects; * 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 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. + * <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. */ @@ -91,28 +91,28 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private static final String LOG_TAG = "TimeZoneDetectorStrategy"; private static final boolean DBG = false; - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) + @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_PHONE = 1; + 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 phone suggestion. + * The abstract score for an empty or invalid telephony suggestion. * - * Used to score phone suggestions where there is no zone. + * Used to score telephony suggestions where there is no zone. */ @VisibleForTesting - public static final int PHONE_SCORE_NONE = 0; + public static final int TELEPHONY_SCORE_NONE = 0; /** - * The abstract score for a low quality phone suggestion. + * 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 @@ -121,10 +121,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * You would have to be quite desperate to want to use this choice. */ @VisibleForTesting - public static final int PHONE_SCORE_LOW = 1; + public static final int TELEPHONY_SCORE_LOW = 1; /** - * The abstract score for a medium quality phone suggestion. + * 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 @@ -132,36 +132,38 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * switch to DST at the wrong time and (for example) their calendar events. */ @VisibleForTesting - public static final int PHONE_SCORE_MEDIUM = 2; + public static final int TELEPHONY_SCORE_MEDIUM = 2; /** - * The abstract score for a high quality phone suggestion. + * 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 PHONE_SCORE_HIGH = 3; + public static final int TELEPHONY_SCORE_HIGH = 3; /** - * The abstract score for a highest quality phone suggestion. + * 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 PHONE_SCORE_HIGHEST = 4; + public static final int TELEPHONY_SCORE_HIGHEST = 4; /** - * The threshold at which phone suggestions are good enough to use to set the device's time + * The threshold at which telephony 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; + public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_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; + /** + * 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; @@ -174,13 +176,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat 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. + * 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, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex = - new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE); + private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion> + mSuggestionBySlotIndex = + new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE); /** * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. @@ -205,42 +208,43 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override - public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) { + public synchronized void suggestTelephonyTimeZone( + @NonNull TelephonyTimeZoneSuggestion suggestion) { if (DBG) { - Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion); + Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion); } Objects.requireNonNull(suggestion); // Score the suggestion. - int score = scorePhoneSuggestion(suggestion); - QualifiedPhoneTimeZoneSuggestion scoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(suggestion, score); + 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 phone time suggested. suggestion=" + suggestion; + String reason = "New telephony time suggested. suggestion=" + suggestion; doAutoTimeZoneDetection(reason); } - private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) { + private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) { int score; if (suggestion.getZoneId() == null) { - score = PHONE_SCORE_NONE; + 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 = PHONE_SCORE_HIGHEST; + score = TELEPHONY_SCORE_HIGHEST; } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) { - score = PHONE_SCORE_HIGH; + 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 = PHONE_SCORE_MEDIUM; + score = TELEPHONY_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; + score = TELEPHONY_SCORE_LOW; } else { throw new AssertionError(); } @@ -248,9 +252,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } /** - * 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. + * 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) { @@ -259,35 +263,37 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return; } - QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); + QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion = + findBestTelephonySuggestion(); // Work out what to do with the best suggestion. - if (bestPhoneSuggestion == null) { - // There is no phone suggestion available at all. Become un-opinionated. + 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 phone suggestion." + 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 = bestPhoneSuggestion.suggestion.getZoneId(); + 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." - + " bestPhoneSuggestion=" + bestPhoneSuggestion + + " bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason; Slog.i(LOG_TAG, cause); - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause); + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, newZoneId, cause); return; } - boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD; + boolean suggestionGoodEnough = + bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD; if (!suggestionGoodEnough) { if (DBG) { Slog.d(LOG_TAG, "Best suggestion not good enough." - + " bestPhoneSuggestion=" + bestPhoneSuggestion + + " bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason); } return; @@ -297,16 +303,16 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // zone ID. if (newZoneId == null) { Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" - + " bestPhoneSuggestion=" + bestPhoneSuggestion + + " bestTelephonySuggestion=" + bestTelephonySuggestion + " detectionReason=" + detectionReason); return; } - String zoneId = bestPhoneSuggestion.suggestion.getZoneId(); + String zoneId = bestTelephonySuggestion.suggestion.getZoneId(); String cause = "Found good suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion + + ", bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason; - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause); + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, zoneId, cause); } @GuardedBy("this") @@ -372,15 +378,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @GuardedBy("this") @Nullable - private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() { - QualifiedPhoneTimeZoneSuggestion bestSuggestion = null; + private QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestion() { + QualifiedTelephonyTimeZoneSuggestion 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 + // 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++) { - QualifiedPhoneTimeZoneSuggestion candidateSuggestion = + QualifiedTelephonyTimeZoneSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); if (candidateSuggestion == null) { // Unexpected @@ -404,13 +410,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } /** - * 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 QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() { - return findBestPhoneSuggestion(); + public synchronized QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestionForTests() { + return findBestTelephonySuggestion(); } @Override @@ -447,7 +453,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mTimeZoneChangesLog.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 @@ -459,18 +465,19 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting - public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) { + public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion( + int slotIndex) { return mSuggestionBySlotIndex.get(slotIndex); } /** - * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata. + * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. */ @VisibleForTesting - public static class QualifiedPhoneTimeZoneSuggestion { + public static class QualifiedTelephonyTimeZoneSuggestion { @VisibleForTesting - public final PhoneTimeZoneSuggestion suggestion; + public final TelephonyTimeZoneSuggestion suggestion; /** * The score the suggestion has been given. This can be used to rank against other @@ -480,7 +487,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public final int score; @VisibleForTesting - public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) { + public QualifiedTelephonyTimeZoneSuggestion( + TelephonyTimeZoneSuggestion suggestion, int score) { this.suggestion = suggestion; this.score = score; } @@ -493,7 +501,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat if (o == null || getClass() != o.getClass()) { return false; } - QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o; + QualifiedTelephonyTimeZoneSuggestion that = (QualifiedTelephonyTimeZoneSuggestion) o; return score == that.score && suggestion.equals(that.suggestion); } @@ -505,7 +513,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat @Override public String toString() { - return "QualifiedPhoneTimeZoneSuggestion{" + 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/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/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/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 44a6fc936961..0733a72446a2 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -74,11 +74,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,26 +88,76 @@ 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(); @@ -128,17 +177,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 +195,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 +222,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); - + 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); } 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..6e1f46bb5e42 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7558,6 +7558,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/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/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/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 79503f797318..43e773870124 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -293,13 +293,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); } 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/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/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/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 e0c6d6a36d0c..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; @@ -5446,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 { @@ -5487,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, 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..9d2091a8578e 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 @@ -232,7 +232,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 +263,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 +290,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()); @@ -341,6 +343,7 @@ public final class DataManagerTest { @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 +371,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/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/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 218f43c9495d..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,7 +30,7 @@ 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.HandlerThread; @@ -80,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.waitForMessagesToBeProcessed(); - mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); + mStubbedTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion); } @Test(expected = SecurityException.class) @@ -199,10 +199,10 @@ public class TimeDetectorServiceTest { mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); } - private static PhoneTimeSuggestion createPhoneTimeSuggestion() { + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() { int slotIndex = 1234; TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L); - return new PhoneTimeSuggestion.Builder(slotIndex) + return new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(timeValue) .build(); } @@ -220,7 +220,7 @@ 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 mHandleAutoTimeDetectionChangedCalled; @@ -231,8 +231,8 @@ public class TimeDetectorServiceTest { } @Override - public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) { - mLastPhoneSuggestion = timeSuggestion; + public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) { + mLastTelephonySuggestion = timeSuggestion; } @Override @@ -256,15 +256,15 @@ public class TimeDetectorServiceTest { } void resetCallTracking() { - mLastPhoneSuggestion = null; + mLastTelephonySuggestion = null; mLastManualSuggestion = null; mLastNetworkSuggestion = null; mHandleAutoTimeDetectionChangedCalled = false; mDumpCalled = false; } - void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) { - assertEquals(expectedSuggestion, mLastPhoneSuggestion); + void verifySuggestTelephonyTimeCalled(TelephonyTimeSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); } public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) { 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/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index 3e7d40a0335a..039c2b4933e9 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -29,7 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; import android.content.pm.PackageManager; import android.os.HandlerThread; @@ -76,35 +76,35 @@ public class TimeZoneDetectorServiceTest { } @Test(expected = SecurityException.class) - public void testSuggestPhoneTime_withoutPermission() { + public void testSuggestTelephonyTime_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion(); + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); try { - mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); 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 testSuggestPhoneTimeZone() throws Exception { + public void testSuggestTelephonyTimeZone() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion(); - mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); 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.waitForMessagesToBeProcessed(); - mStubbedTimeZoneDetectorStrategy.verifySuggestPhoneTimeZoneCalled(timeZoneSuggestion); + mStubbedTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion); } @Test(expected = SecurityException.class) @@ -165,12 +165,12 @@ public class TimeZoneDetectorServiceTest { mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled(); } - private static PhoneTimeZoneSuggestion createPhoneTimeZoneSuggestion() { + private static TelephonyTimeZoneSuggestion createTelephonyTimeZoneSuggestion() { int slotIndex = 1234; - return new PhoneTimeZoneSuggestion.Builder(slotIndex) + return new TelephonyTimeZoneSuggestion.Builder(slotIndex) .setZoneId("TestZoneId") - .setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET) - .setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE) + .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET) + .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE) .build(); } @@ -181,14 +181,14 @@ public class TimeZoneDetectorServiceTest { private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { // Call tracking. - private PhoneTimeZoneSuggestion mLastPhoneSuggestion; + private TelephonyTimeZoneSuggestion mLastTelephonySuggestion; private ManualTimeZoneSuggestion mLastManualSuggestion; private boolean mHandleAutoTimeZoneDetectionChangedCalled; private boolean mDumpCalled; @Override - public void suggestPhoneTimeZone(PhoneTimeZoneSuggestion timeZoneSuggestion) { - mLastPhoneSuggestion = timeZoneSuggestion; + public void suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) { + mLastTelephonySuggestion = timeZoneSuggestion; } @Override @@ -207,14 +207,14 @@ public class TimeZoneDetectorServiceTest { } void resetCallTracking() { - mLastPhoneSuggestion = null; + mLastTelephonySuggestion = null; mLastManualSuggestion = null; mHandleAutoTimeZoneDetectionChangedCalled = false; mDumpCalled = false; } - void verifySuggestPhoneTimeZoneCalled(PhoneTimeZoneSuggestion expectedSuggestion) { - assertEquals(expectedSuggestion, mLastPhoneSuggestion); + void verifySuggestTelephonyTimeZoneCalled(TelephonyTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); } public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) { diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 1e387110ed43..ba309679e47a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.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.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGH; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGHEST; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_LOW; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_MEDIUM; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_NONE; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.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.TimeZoneDetectorStrategyImpl.QualifiedPhoneTimeZoneSuggestion; +import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; import org.junit.Before; import org.junit.Test; @@ -58,24 +58,24 @@ 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 TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy; @@ -89,76 +89,82 @@ public class TimeZoneDetectorStrategyImplTest { } @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 TimeZoneDetectorStrategyImplTest { 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 TimeZoneDetectorStrategyImplTest { // 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 TimeZoneDetectorStrategyImplTest { // 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,125 +247,128 @@ public class TimeZoneDetectorStrategyImplTest { 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)); } } @@ -375,21 +384,21 @@ public class TimeZoneDetectorStrategyImplTest { 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 TimeZoneDetectorStrategyImplTest { // 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,12 +442,12 @@ public class TimeZoneDetectorStrategyImplTest { 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 @@ -565,9 +574,11 @@ public class TimeZoneDetectorStrategyImplTest { 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; } @@ -582,7 +593,7 @@ public class TimeZoneDetectorStrategyImplTest { return this; } - Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) { + Script verifyTimeZoneSetAndReset(TelephonyTimeZoneSuggestion suggestion) { mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId()); mFakeTimeZoneDetectorStrategyCallback.commitAllChanges(); return this; @@ -611,8 +622,8 @@ public class TimeZoneDetectorStrategyImplTest { 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/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/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 078347e96a07..a9a20f675f90 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -139,6 +139,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); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 672352211089..e3d031dfa476 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -11290,14 +11290,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 +11320,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/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/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b4f32e75fd01..a0e98d0e1f23 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -6446,14 +6446,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, 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)); @@ -6476,8 +6478,10 @@ public class ConnectivityServiceTest { mService.registerConnectivityDiagnosticsCallback( 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( @@ -6635,8 +6639,11 @@ public class ConnectivityServiceTest { public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception { setUpConnectivityDiagnosticsCallback(); - // Wait for onConnectivityReport to fire - verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)) + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onConnectivityReport fired + verify(mConnectivityDiagnosticsCallback) .onConnectivityReport(any(ConnectivityReport.class)); } @@ -6648,9 +6655,11 @@ public class ConnectivityServiceTest { // cellular network agent mCellNetworkAgent.notifyDataStallSuspected(); - // Wait for onDataStallSuspected to fire - verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)) - .onDataStallSuspected(any(DataStallReport.class)); + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onDataStallSuspected fired + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class)); } @Test @@ -6661,15 +6670,21 @@ public class ConnectivityServiceTest { final boolean hasConnectivity = true; mService.reportNetworkConnectivity(n, hasConnectivity); - // Wait for onNetworkConnectivityReported to fire - verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)) + // 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, timeout(TIMEOUT_MS)) + 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 d3958a65c704..a251c053e004 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -30,7 +30,7 @@ cc_binary_host { "utils.cpp", ], cflags: [ - "-DSTATS_SCHEMA_LEGACY", + //"-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/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/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/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/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(); |