diff options
861 files changed, 30310 insertions, 11701 deletions
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index c12f5b4395ec..56d91b2e4bc5 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -41,7 +41,6 @@ gensrcs { ":libtombstone_proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", - ":service-permission-streaming-proto-sources", ], output_extension: "srcjar", } @@ -72,7 +71,6 @@ gensrcs { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/**/*.proto", - ":service-permission-streaming-proto-sources", ], output_extension: "proto.h", @@ -91,7 +89,6 @@ java_library_host { "cmds/statsd/src/**/*.proto", "core/proto/**/*.proto", "libs/incident/proto/**/*.proto", - ":service-permission-streaming-proto-sources", ], proto: { include_dirs: [ @@ -126,7 +123,6 @@ java_library { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/proto/android/os/**/*.proto", - ":service-permission-streaming-proto-sources", ], // Protos have lots of MissingOverride and similar. errorprone: { @@ -148,7 +144,6 @@ java_library { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/proto/android/os/**/*.proto", - ":service-permission-streaming-proto-sources", ], exclude_srcs: [ "core/proto/android/privacy.proto", @@ -184,7 +179,6 @@ cc_defaults { ":libstats_atom_enum_protos", ":libstats_atom_message_protos", "core/proto/**/*.proto", - ":service-permission-streaming-proto-sources", ], } diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index bb57161b4f6b..4e24909f5d3b 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -16,6 +16,7 @@ <application> <uses-library android:name="android.test.runner" /> + <profileable android:shell="true" /> <activity android:name="android.perftests.utils.PerfTestActivity" android:exported="true"> <intent-filter> diff --git a/apct-tests/perftests/core/AndroidTest.xml b/apct-tests/perftests/core/AndroidTest.xml index 4f8ee2927d51..86f41e1f496c 100644 --- a/apct-tests/perftests/core/AndroidTest.xml +++ b/apct-tests/perftests/core/AndroidTest.xml @@ -27,6 +27,11 @@ <option name="test-file-name" value="CorePerfTests.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="pull-pattern-keys" value="perfetto_file_path" /> </metrics_collector> diff --git a/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml b/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml new file mode 100644 index 000000000000..9005d6fa9e76 --- /dev/null +++ b/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml @@ -0,0 +1,110 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/linear_layout_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="10dp" > + + <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText" + android:id="@+id/remote_input_text" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_weight="1" + android:paddingTop="14dp" + android:paddingStart="4dp" + android:paddingBottom="16dp" + android:paddingEnd="12dp" + android:layout_gravity="start|center_vertical" + android:textAppearance="?android:attr/textAppearance" + android:textSize="16sp" + android:background="@null" + android:maxLines="4" + android:ellipsize="start" + android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences" + android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" /> + + <ImageButton + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + style="@android:style/MediaButton.Previous" + id="@+id/my_image_button_previous" + /> + + <ImageButton + id="@+id/my_image_button_next" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + style="@android:style/MediaButton.Next" + /> + + <LinearLayout + id="@+id/my_linear_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="10dp" > + + <ImageButton + android:id="@+id/button3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true" /> + + <LinearLayout + id="@+id/my_inner_linear_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="10dp" > + + <ImageButton + android:id="@+id/button5" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="true" /> + </LinearLayout> + + <ImageButton + android:id="@+id/button4" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/button2" + android:layout_centerHorizontal="true" /> + + <ImageButton + android:id="@+id/button6" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@+id/button4" + android:layout_centerHorizontal="true" /> + + <ImageButton + android:id="@+id/button7" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/button" + android:layout_toEndOf="@+id/button" + android:layout_toRightOf="@+id/button" /> + </LinearLayout> +</LinearLayout> diff --git a/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java b/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java new file mode 100644 index 000000000000..bdce902e4aef --- /dev/null +++ b/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.content.Context; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.text.TextUtils; +import android.util.Log; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.perftests.core.R; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.function.Function; +import java.util.function.Supplier; + +@LargeTest +public class XmlBlockBenchmark { + private static final String TAG = "XmlBlockBenchmark"; + private static final String NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"; + + @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + private XmlBlock.Parser mParser; + + private void cleanCache() { + if (mParser != null) { + mParser.close(); + } + + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final Resources resources = context.getResources(); + resources.getImpl().clearAllCaches(); + Log.d(TAG, "cleanCache"); + } + + private XmlBlock.Parser getNewParser() { + cleanCache(); + + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final Resources resources = context.getResources(); + return (XmlBlock.Parser) resources.getXml(R.layout.linear_layout_for_xmlblock_benchmark); + } + + @Before + public void setUp() { + mParser = getNewParser(); + } + + @After + public void tearDown() { + cleanCache(); + } + + int safeNext() throws XmlPullParserException, IOException { + while (true) { + int parseState = mParser.next(); + if (parseState == START_TAG) { + return parseState; + } else if (parseState == END_DOCUMENT) { + mParser = getNewParser(); + } + } + } + + @Test + public void throwNpeCausedByNullDocument() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + mParser.close(); + while (state.keepRunning()) { + try { + mParser.getClassAttribute(); + } catch (NullPointerException e) { + continue; + } + Assert.fail("It shouldn't be here!"); + } + } + + @Test + public void getNext() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + int parseState = mParser.next(); + state.pauseTiming(); + if (parseState == END_DOCUMENT) { + mParser = getNewParser(); + } + state.resumeTiming(); + } + } + + private <T> void benchmarkTagFunction(BenchmarkState state, String name, + Supplier<T> measureTarget) + throws XmlPullParserException, IOException { + while (state.keepRunning()) { + state.pauseTiming(); + int parseState = safeNext(); + + if (parseState != END_DOCUMENT) { + final String tagName = mParser.getName(); + state.resumeTiming(); + final T value = measureTarget.get(); + state.pauseTiming(); + Log.d(TAG, + TextUtils.formatSimple("%s() in tag %s is %s", name, tagName, value)); + } + state.resumeTiming(); + } + } + + @Test + public void getNamespace() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getNamespace", () -> mParser.getNamespace()); + } + + @Test + public void getName() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getName", () -> mParser.getName()); + } + + @Test + public void getText() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getText", () -> mParser.getText()); + } + + @Test + public void getLineNumber() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getLineNumber", () -> mParser.getLineNumber()); + } + + @Test + public void getAttributeCount() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getAttributeCount", () -> mParser.getAttributeCount()); + } + + private <T> void benchmarkAttributeFunction(BenchmarkState state, String name, + Function<Integer, T> measureTarget) + throws XmlPullParserException, IOException { + boolean needNext = true; + boolean needGetCount = false; + int attributeCount = 0; + int i = 0; + while (state.keepRunning()) { + state.pauseTiming(); + if (needNext) { + int parseState = safeNext(); + if (parseState == START_TAG) { + needNext = false; + needGetCount = true; + } + } + + if (needGetCount) { + attributeCount = mParser.getAttributeCount(); + needGetCount = false; + i = 0; + } + + if (i < attributeCount) { + final String tagName = mParser.getName(); + final String attributeName = mParser.getAttributeName(i); + state.resumeTiming(); + final T value = measureTarget.apply(i); + state.pauseTiming(); + Log.d(TAG, + TextUtils.formatSimple("%s(%d:%s) in tag %s is %s", name, i, attributeName, + tagName, value)); + i++; + } + + if (i >= attributeCount) { + needNext = true; + } + state.resumeTiming(); + } + } + + @Test + public void getAttributeNamespace() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkAttributeFunction(state, "getAttributeNamespace", + attributeIndex -> mParser.getAttributeNamespace(attributeIndex)); + } + + @Test + public void getAttributeName() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkAttributeFunction(state, "getAttributeName", + attributeIndex -> mParser.getAttributeName(attributeIndex)); + } + + @Test + public void getAttributeNameResource() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkAttributeFunction(state, "getAttributeNameResource", + attributeIndex -> mParser.getAttributeNameResource(attributeIndex)); + } + + /** + * benchmark {@link android.content.res.XmlBlock#nativeGetAttributeDataType(long, int)} and + * {@link android.content.res.XmlBlock#nativeGetAttributeData(long, int)} + */ + @Test + public void getAttributeDataXXX() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkAttributeFunction(state, "getAttributeDataXXX", + attributeIndex -> mParser.getAttributeValue(attributeIndex)); + } + + @Test + public void getSourceResId() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getSourceResId", () -> mParser.getSourceResId()); + } + + @Test + public void getIdAttribute() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getIdAttribute", () -> mParser.getIdAttribute()); + } + + @Test + public void getClassAttribute() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getClassAttribute", () -> mParser.getClassAttribute()); + } + + @Test + public void getStyleAttribute() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getStyleAttribute", () -> mParser.getStyleAttribute()); + } + + @Test + public void getAttributeIndex() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + benchmarkTagFunction(state, "getAttributeValue", + () -> mParser.getAttributeValue(NAMESPACE_ANDROID, "layout_width")); + } + + @Test + public void parseOneXmlDocument() throws XmlPullParserException, IOException { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + mParser = getNewParser(); + state.resumeTiming(); + + int parseState; + while ((parseState = mParser.next()) != END_DOCUMENT) { + if (parseState == START_DOCUMENT) { + state.pauseTiming(); + Log.d(TAG, "parseOneXmlDocument: start document"); + state.resumeTiming(); + } else if (parseState == START_TAG) { + final String tagName = mParser.getName(); + state.pauseTiming(); + Log.d(TAG, TextUtils.formatSimple("parseOneXmlDocument: tag %s {[", tagName)); + state.resumeTiming(); + for (int i = 0, count = mParser.getAttributeCount(); i < count; i++) { + final String attributeName = mParser.getAttributeName(i); + final String attributeValue = mParser.getAttributeValue(i); + + state.pauseTiming(); + Log.d(TAG, TextUtils.formatSimple( + "parseOneXmlDocument: attribute %d {%s = %s},", i, attributeName, + attributeValue)); + state.resumeTiming(); + } + state.pauseTiming(); + Log.d(TAG, "parseOneXmlDocument: ]"); + state.resumeTiming(); + } else if (parseState == END_TAG) { + state.pauseTiming(); + Log.d(TAG, "parseOneXmlDocument: }"); + state.resumeTiming(); + } else { + final String text = mParser.getText(); + state.pauseTiming(); + Log.d(TAG, TextUtils.formatSimple( + "parseOneXmlDocument: parseState = %d, text = %s", parseState, text)); + state.resumeTiming(); + } + } + } + } +} diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index 3452f587e3ff..6d1e6d0cbd73 100644 --- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -28,16 +28,16 @@ import androidx.test.filters.LargeTest import com.android.internal.util.ConcurrentUtils import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.parsing.ParsingPackageUtils +import java.io.File +import java.io.FileOutputStream +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.TimeUnit import libcore.io.IoUtils import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.junit.runners.Parameterized -import java.io.File -import java.io.FileOutputStream -import java.util.concurrent.ArrayBlockingQueue -import java.util.concurrent.TimeUnit @LargeTest @RunWith(Parameterized::class) @@ -180,8 +180,8 @@ public class PackageParsingPerfTest { protected abstract fun parseImpl(file: File): PackageType } - class ParallelParser1(private val cacher: PackageCacher1? = null) - : ParallelParser<PackageParser.Package>(cacher) { + class ParallelParser1(private val cacher: PackageCacher1? = null) : + ParallelParser<PackageParser.Package>(cacher) { val parser = PackageParser().apply { setCallback { true } } @@ -189,8 +189,8 @@ public class PackageParsingPerfTest { override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null) } - class ParallelParser2(cacher: PackageCacher2? = null) - : ParallelParser<PackageImpl>(cacher) { + class ParallelParser2(cacher: PackageCacher2? = null) : + ParallelParser<PackageImpl>(cacher) { val input = ThreadLocal.withInitial { // For testing, just disable enforcement to avoid hooking up to compat framework ParseTypeImpl(ParseInput.Callback { _, _, _ -> false }) @@ -218,7 +218,7 @@ public class PackageParsingPerfTest { }) override fun parseImpl(file: File) = - parser.parsePackage(input.get()!!.reset(), file, 0, null).result + parser.parsePackage(input.get()!!.reset(), file, 0).result as PackageImpl } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 5b61f9abc357..22f70ce8e0e6 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -57,6 +57,8 @@ import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_R import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManagerInternal; @@ -289,6 +291,7 @@ public class AlarmManagerService extends SystemService { final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); IBinder.DeathRecipient mListenerDeathRecipient; Intent mTimeTickIntent; + Bundle mTimeTickOptions; IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; boolean mInteractive = true; @@ -1909,7 +1912,9 @@ public class AlarmManagerService extends SystemService { Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); - + mTimeTickOptions = BroadcastOptions + .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK)) + .toBundle(); mTimeTickTrigger = new IAlarmListener.Stub() { @Override public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException { @@ -1921,8 +1926,8 @@ public class AlarmManagerService extends SystemService { // takes care of this automatically, but we're using the direct internal // interface here rather than that client-side wrapper infrastructure. mHandler.post(() -> { - getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL); - + getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL, null, + mTimeTickOptions); try { callback.alarmComplete(this); } catch (RemoteException e) { /* local method call */ } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 4d5eef2f65f5..1e13dbf9b057 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1404,8 +1404,31 @@ public class JobSchedulerService extends com.android.server.SystemService } mChangedJobList.remove(cancelled); // Cancel if running. - mConcurrencyManager.stopJobOnServiceContextLocked( + final boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked( cancelled, reason, internalReasonCode, debugReason); + // If the job was running, the JobServiceContext should log with state FINISHED. + if (!wasRunning) { + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, + cancelled.getSourceUid(), null, cancelled.getBatteryName(), + FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED, + internalReasonCode, cancelled.getStandbyBucket(), + cancelled.getJobId(), + cancelled.hasChargingConstraint(), + cancelled.hasBatteryNotLowConstraint(), + cancelled.hasStorageNotLowConstraint(), + cancelled.hasTimingDelayConstraint(), + cancelled.hasDeadlineConstraint(), + cancelled.hasIdleConstraint(), + cancelled.hasConnectivityConstraint(), + cancelled.hasContentTriggerConstraint(), + cancelled.isRequestedExpeditedJob(), + /* isRunningAsExpeditedJob */ false, + reason, + cancelled.getJob().isPrefetch(), + cancelled.getJob().getPriority(), + cancelled.getEffectivePriority(), + cancelled.getNumFailures()); + } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index dfa1442a3192..fcfb45c5f1df 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -742,6 +742,10 @@ public final class JobStore { } } catch (XmlPullParserException | IOException e) { Slog.wtf(TAG, "Error jobstore xml.", e); + } catch (Exception e) { + // Crashing at this point would result in a boot loop, so live with a general + // Exception for system stability's sake. + Slog.wtf(TAG, "Unexpected exception", e); } finally { if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. mPersistInfo.countAllJobsLoaded = numJobs; @@ -890,6 +894,9 @@ public final class JobStore { } catch (IOException e) { Slog.d(TAG, "Error I/O Exception.", e); return null; + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Constraints contained invalid data", e); + return null; } parser.next(); // Consume </constraints> @@ -986,8 +993,14 @@ public final class JobStore { return null; } - PersistableBundle extras = PersistableBundle.restoreFromXml(parser); - jobBuilder.setExtras(extras); + final PersistableBundle extras; + try { + extras = PersistableBundle.restoreFromXml(parser); + jobBuilder.setExtras(extras); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Persisted extras contained invalid data", e); + return null; + } parser.nextTag(); // Consume </extras> final JobInfo builtJob; diff --git a/api/Android.mk b/api/Android.mk new file mode 100644 index 000000000000..ce5f995033c5 --- /dev/null +++ b/api/Android.mk @@ -0,0 +1,2 @@ +.PHONY: checkapi +checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index 18ec5a407b24..4f8faca59e4c 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -43,6 +43,7 @@ cc_defaults { "-modernize-return-braced-init-list", "-modernize-use-default-member-init", "-modernize-use-equals-default", + "-modernize-use-emplace", "-modernize-use-nodiscard", "-modernize-use-override", "-modernize-use-trailing-return-type", diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 4efaeea74274..444f91d144a2 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -222,7 +222,7 @@ std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& strea || !Read32(stream, &entry_count)) { return nullptr; } - target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count)); + target_inline_entries.emplace_back(target_entry, entry_offset, entry_count); } // Read the inline overlay resource values @@ -241,7 +241,7 @@ std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& strea || !Read32(stream, &value.data_value)) { return nullptr; } - target_values.emplace_back(std::make_pair(config_index, value)); + target_values.emplace_back(config_index, value); } // Read the configurations diff --git a/core/api/current.txt b/core/api/current.txt index 19713cfebf6c..3331603f586e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15684,6 +15684,7 @@ package android.graphics { method public static android.graphics.Typeface createFromFile(@Nullable String); method public static android.graphics.Typeface defaultFromStyle(int); method public int getStyle(); + method @Nullable public final String getSystemFontFamilyName(); method @IntRange(from=0, to=1000) public int getWeight(); method public final boolean isBold(); method public final boolean isItalic(); @@ -20062,6 +20063,7 @@ package android.media { field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc + field public static final int CHANNEL_OUT_6POINT1 = 1276; // 0x4fc field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc @@ -41550,6 +41552,10 @@ package android.telephony { field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long"; field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; + field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long"; + field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long"; + field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long"; + field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING = "premium_capability_purchase_url_string"; field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool"; field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array"; field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; @@ -41582,6 +41588,7 @@ package android.telephony { field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string"; + field public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY = "supported_premium_capabilities_int_array"; field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool"; field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool"; @@ -43073,6 +43080,7 @@ package android.telephony { method @NonNull public int[] getThresholds(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalThresholdInfo> CREATOR; + field public static final int SIGNAL_MEASUREMENT_TYPE_ECNO = 9; // 0x9 field public static final int SIGNAL_MEASUREMENT_TYPE_RSCP = 2; // 0x2 field public static final int SIGNAL_MEASUREMENT_TYPE_RSRP = 3; // 0x3 field public static final int SIGNAL_MEASUREMENT_TYPE_RSRQ = 4; // 0x4 @@ -43645,6 +43653,7 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isModemEnabledForSlot(int); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported(); method public boolean isNetworkRoaming(); + method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int); method public boolean isRadioInterfaceCapabilitySupported(@NonNull String); method public boolean isRttSupported(); method public boolean isSmsCapable(); @@ -43653,6 +43662,7 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method @Deprecated public void listen(android.telephony.PhoneStateListener, int); + method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public void purchasePremiumCapability(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void rebootModem(); method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback); method public void registerTelephonyCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback); @@ -43822,6 +43832,20 @@ package android.telephony { field public static final int PHONE_TYPE_GSM = 1; // 0x1 field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 + field public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1; // 0x1 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4; // 0x4 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 5; // 0x5 field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2 field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3 field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4 @@ -49231,6 +49255,7 @@ package android.view { field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1 field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2 field public static final int CLASSIFICATION_NONE = 0; // 0x0 + field public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3; // 0x3 field @NonNull public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR; field public static final int EDGE_BOTTOM = 2; // 0x2 field public static final int EDGE_LEFT = 4; // 0x4 @@ -53519,8 +53544,8 @@ package android.view.inputmethod { public final class InsertGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { method public int describeContents(); - method @Nullable public android.graphics.PointF getInsertionPoint(); - method @Nullable public String getTextToInsert(); + method @NonNull public android.graphics.PointF getInsertionPoint(); + method @NonNull public String getTextToInsert(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertGesture> CREATOR; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 887af1f4f350..fb2c9e43497f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1168,9 +1168,11 @@ package android.hardware.camera2 { package android.hardware.devicestate { public final class DeviceStateManager { + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride(); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest(); method @NonNull public int[] getSupportedStates(); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff @@ -1221,6 +1223,7 @@ package android.hardware.display { method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode(); method @NonNull public int[] getUserDisabledHdrTypes(); method public boolean isMinimalPostProcessingRequested(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode); method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int); @@ -2883,7 +2886,6 @@ package android.view { ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String); method @NonNull public static android.os.IBinder getInternalDisplayToken(); method public boolean isSameSurface(@NonNull android.view.SurfaceControl); - method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]); } public class SurfaceControlViewHost { @@ -3432,6 +3434,7 @@ package android.window { method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams); method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken); method public int describeContents(); + method @NonNull public android.window.WindowContainerTransaction finishActivity(@NonNull android.os.IBinder); method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken); method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index dfef279be7c5..8ae16df64ace 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4667,7 +4667,7 @@ public class ActivityManager { * @hide */ public static void broadcastStickyIntent(Intent intent, int userId) { - broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId); + broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId); } /** @@ -4676,11 +4676,20 @@ public class ActivityManager { * @hide */ public static void broadcastStickyIntent(Intent intent, int appOp, int userId) { + broadcastStickyIntent(intent, appOp, null, userId); + } + + /** + * Convenience for sending a sticky broadcast. For internal use only. + * + * @hide + */ + public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) { try { getService().broadcastIntentWithFeature( null, null, intent, null, null, Activity.RESULT_OK, null, null, null /*requiredPermissions*/, null /*excludedPermissions*/, - null /*excludedPackages*/, appOp, null, false, true, userId); + null /*excludedPackages*/, appOp, options, false, true, userId); } catch (RemoteException ex) { } } diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index aa5fa5b19117..c2df8022af0f 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -27,12 +27,15 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.EnabledSince; import android.content.Intent; +import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; import android.os.PowerExemptionManager.TempAllowListType; +import java.util.Objects; + /** * Helper class for building an options Bundle that can be used with * {@link android.content.Context#sendBroadcast(android.content.Intent) @@ -55,6 +58,7 @@ public class BroadcastOptions extends ComponentOptions { private boolean mRequireCompatChangeEnabled = true; private boolean mIsAlarmBroadcast = false; private long mIdForResponseEvent; + private @Nullable IntentFilter mRemoveMatchingFilter; /** * Change ID which is invalid. @@ -180,11 +184,25 @@ public class BroadcastOptions extends ComponentOptions { private static final String KEY_ID_FOR_RESPONSE_EVENT = "android:broadcast.idForResponseEvent"; + /** + * Corresponds to {@link #setRemoveMatchingFilter}. + */ + private static final String KEY_REMOVE_MATCHING_FILTER = + "android:broadcast.removeMatchingFilter"; + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; } + /** {@hide} */ + public static @NonNull BroadcastOptions makeRemovingMatchingFilter( + @NonNull IntentFilter removeMatchingFilter) { + BroadcastOptions opts = new BroadcastOptions(); + opts.setRemoveMatchingFilter(removeMatchingFilter); + return opts; + } + private BroadcastOptions() { super(); resetTemporaryAppAllowlist(); @@ -216,6 +234,8 @@ public class BroadcastOptions extends ComponentOptions { mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false); + mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER, + IntentFilter.class); } /** @@ -596,6 +616,29 @@ public class BroadcastOptions extends ComponentOptions { } /** + * When enqueuing this broadcast, remove all pending broadcasts previously + * sent by this app which match the given filter. + * <p> + * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want + * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts. + * + * @hide + */ + public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) { + mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter); + } + + /** @hide */ + public void clearRemoveMatchingFilter() { + mRemoveMatchingFilter = null; + } + + /** @hide */ + public @Nullable IntentFilter getRemoveMatchingFilter() { + return mRemoveMatchingFilter; + } + + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. @@ -640,6 +683,9 @@ public class BroadcastOptions extends ComponentOptions { if (mIdForResponseEvent != 0) { b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent); } + if (mRemoveMatchingFilter != null) { + b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter); + } return b.isEmpty() ? null : b; } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 80121b729779..0e1b47f65561 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1094,7 +1094,7 @@ class ContextImpl extends Context { && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { throw new AndroidRuntimeException( - "Calling startActivity() from outside of an Activity " + "Calling startActivity() from outside of an Activity" + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } @@ -1128,7 +1128,7 @@ class ContextImpl extends Context { public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( - "Calling startActivities() from outside of an Activity " + "Calling startActivities() from outside of an Activity" + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + " Is this really what you want?"); } @@ -1142,7 +1142,7 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( - "Calling startActivities() from outside of an Activity " + "Calling startActivities() from outside of an Activity" + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + " Is this really what you want?"); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 4da957c17a56..e5c080acbedb 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -29,8 +29,6 @@ import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.IAmbientContextManager; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; -import android.app.cloudsearch.CloudSearchManager; -import android.app.cloudsearch.ICloudSearchManager; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.job.JobSchedulerFrameworkInitializer; @@ -1220,17 +1218,6 @@ public final class SystemServiceRegistry { } }); - registerService(Context.CLOUDSEARCH_SERVICE, CloudSearchManager.class, - new CachedServiceFetcher<CloudSearchManager>() { - @Override - public CloudSearchManager createService(ContextImpl ctx) - throws ServiceNotFoundException { - IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE); - return b == null ? null : - new CloudSearchManager(ICloudSearchManager.Stub.asInterface(b)); - } - }); - registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class, new CachedServiceFetcher<AppPredictionManager>() { @Override diff --git a/core/java/android/app/cloudsearch/CloudSearchManager.java b/core/java/android/app/cloudsearch/CloudSearchManager.java index 471e423db458..b7bbf4712842 100644 --- a/core/java/android/app/cloudsearch/CloudSearchManager.java +++ b/core/java/android/app/cloudsearch/CloudSearchManager.java @@ -15,17 +15,15 @@ */ package android.app.cloudsearch; -import static java.util.Objects.requireNonNull; - import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; -import android.os.RemoteException; import java.util.concurrent.Executor; + /** * A {@link CloudSearchManager} is the class having all the information passed to search providers. * @@ -41,7 +39,7 @@ public class CloudSearchManager { /** * Invoked by receiving app with the result of the search. * - * @param request original request for the search. + * @param request original request for the search. * @param response search result. */ void onSearchSucceeded(@NonNull SearchRequest request, @NonNull SearchResponse response); @@ -51,17 +49,15 @@ public class CloudSearchManager { * Each failure is recorded. The client may receive a failure from one provider and * subsequently receive successful searches from other providers * - * @param request original request for the search. + * @param request original request for the search. * @param response search result. */ void onSearchFailed(@NonNull SearchRequest request, @NonNull SearchResponse response); } - private final ICloudSearchManager mService; - /** @hide **/ - public CloudSearchManager(@NonNull ICloudSearchManager service) { - mService = service; + public CloudSearchManager() { + } /** @@ -69,10 +65,9 @@ public class CloudSearchManager { * to the designated cloud lookup services. After the lookup is done, the given * callback will be invoked by the system with the result or lack thereof. * - * @param request request to be searched. + * @param request request to be searched. * @param callbackExecutor where the callback is invoked. - * @param callback invoked when the result is available. - * + * @param callback invoked when the result is available. * @hide */ @SystemApi @@ -80,49 +75,8 @@ public class CloudSearchManager { public void search(@NonNull SearchRequest request, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull CallBack callback) { - try { - mService.search( - requireNonNull(request), - new CallBackWrapper( - requireNonNull(request), - requireNonNull(callback), - requireNonNull(callbackExecutor))); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private final class CallBackWrapper extends - ICloudSearchManagerCallback.Stub { - @NonNull - private final SearchRequest mSearchRequest; - - @NonNull - private final CallBack mCallback; - - @NonNull - private final Executor mCallbackExecutor; - - CallBackWrapper( - SearchRequest searchRequest, - CallBack callback, - Executor callbackExecutor) { - mSearchRequest = searchRequest; - mCallback = callback; - mCallbackExecutor = callbackExecutor; - } - - - @Override - public void onSearchSucceeded(SearchResponse searchResponse) { - mCallbackExecutor.execute( - () -> mCallback.onSearchSucceeded(mSearchRequest, searchResponse)); - } - - @Override - public void onSearchFailed(SearchResponse searchResponse) { - mCallbackExecutor.execute( - () -> mCallback.onSearchFailed(mSearchRequest, searchResponse)); - } + callbackExecutor.execute( + () -> callback.onSearchFailed(request, + new SearchResponse.Builder(SearchResponse.SEARCH_STATUS_UNKNOWN).build())); } } diff --git a/core/java/android/app/cloudsearch/ICloudSearchManager.aidl b/core/java/android/app/cloudsearch/ICloudSearchManager.aidl deleted file mode 100644 index 18f8fc476191..000000000000 --- a/core/java/android/app/cloudsearch/ICloudSearchManager.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2022, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.cloudsearch; - -import android.app.cloudsearch.SearchRequest; -import android.app.cloudsearch.SearchResponse; -import android.app.cloudsearch.ICloudSearchManagerCallback; - -/** - * Used by {@link CloudSearchManager} to tell system server to do search. - * - * @hide - */ -oneway interface ICloudSearchManager { - void search(in SearchRequest request, in ICloudSearchManagerCallback callBack); - - void returnResults(in IBinder token, in String requestId, - in SearchResponse response); -} diff --git a/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl b/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl deleted file mode 100644 index 84771dd4a19b..000000000000 --- a/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2022, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.cloudsearch; - -import android.app.cloudsearch.SearchResponse; - - -/** - * Callback used by system server to notify invoker of {@link CloudSearchManager} of the result - * - * @hide - */ -oneway interface ICloudSearchManagerCallback { - void onSearchSucceeded(in SearchResponse response); - - void onSearchFailed(in SearchResponse response); -} diff --git a/core/java/android/app/cloudsearch/SearchRequest.aidl b/core/java/android/app/cloudsearch/SearchRequest.aidl deleted file mode 100644 index 9f2cdb888bdf..000000000000 --- a/core/java/android/app/cloudsearch/SearchRequest.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2022, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.cloudsearch; - -parcelable SearchRequest; diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java index bf783255b3d9..3725b36a1c7f 100644 --- a/core/java/android/app/cloudsearch/SearchRequest.java +++ b/core/java/android/app/cloudsearch/SearchRequest.java @@ -15,8 +15,6 @@ */ package android.app.cloudsearch; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; @@ -28,7 +26,6 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Objects; /** * A {@link SearchRequest} is the data class having all the information passed to search providers. @@ -39,36 +36,6 @@ import java.util.Objects; public final class SearchRequest implements Parcelable { /** - * Query for search. - */ - @NonNull - private final String mQuery; - - /** - * Expected result offset for pagination. - * - * The default value is 0. - */ - private final int mResultOffset; - - /** - * Expected search result number. - * - * The default value is 10. - */ - private final int mResultNumber; - - /** - * The max acceptable latency. - * - * The default value is 200 milliseconds. - */ - private final float mMaxLatencyMillis; - - @Nullable - private String mId = null; - - /** * List of public static KEYS for the Bundle to mSearchConstraints. mSearchConstraints * contains various constraints specifying the search intent. * @@ -76,121 +43,83 @@ public final class SearchRequest implements Parcelable { */ @Retention(RetentionPolicy.SOURCE) @StringDef(prefix = {"CONSTRAINT_"}, - value = {CONSTRAINT_IS_PRESUBMIT_SUGGESTION, - CONSTRAINT_SEARCH_PROVIDER_FILTER}) - public @interface SearchConstraintKey {} - /** If this is a presubmit suggestion, Boolean value expected. - * presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or - * "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit. + value = {CONSTRAINT_IS_PRESUBMIT_SUGGESTION, + CONSTRAINT_SEARCH_PROVIDER_FILTER}) + public @interface SearchConstraintKey { + } + + /** + * If this is a presubmit suggestion, Boolean value expected. + * presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or + * "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit. */ public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION"; - /** The target search provider list of package names(separated by ;), String value expected. + /** + * The target search provider list of package names(separated by ;), String value expected. * If this is not provided or its value is empty, then no filter will be applied. */ public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "android.app.cloudsearch.SEARCH_PROVIDER_FILTER"; - @NonNull - private Bundle mSearchConstraints; - - /** Auto set by system servier, and the caller cannot set it. - * - * The caller's package name. - * - */ - @NonNull - private String mCallerPackageName; - - private SearchRequest(Parcel in) { - this.mQuery = in.readString(); - this.mResultOffset = in.readInt(); - this.mResultNumber = in.readInt(); - this.mMaxLatencyMillis = in.readFloat(); - this.mSearchConstraints = in.readBundle(); - this.mId = in.readString(); - this.mCallerPackageName = in.readString(); - } - - private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis, - Bundle searchConstraints, String callerPackageName) { - mQuery = query; - mResultOffset = resultOffset; - mResultNumber = resultNumber; - mMaxLatencyMillis = maxLatencyMillis; - mSearchConstraints = searchConstraints; - mCallerPackageName = callerPackageName; + private SearchRequest() { } /** Returns the original query. */ @NonNull public String getQuery() { - return mQuery; + return ""; } /** Returns the result offset. */ public int getResultOffset() { - return mResultOffset; + return 0; } /** Returns the expected number of search results. */ public int getResultNumber() { - return mResultNumber; + return 0; } /** Returns the maximum latency requirement. */ public float getMaxLatencyMillis() { - return mMaxLatencyMillis; + return 0; } /** Returns the search constraints. */ @NonNull public Bundle getSearchConstraints() { - return mSearchConstraints; + return Bundle.EMPTY; } /** Gets the caller's package name. */ @NonNull public String getCallerPackageName() { - return mCallerPackageName; + return ""; } /** Returns the search request id, which is used to identify the request. */ @NonNull public String getRequestId() { - if (mId == null || mId.length() == 0) { - mId = String.valueOf(toString().hashCode()); - } - - return mId; + return ""; } - /** Sets the caller, and this will be set by the system server. + /** + * Sets the caller, and this will be set by the system server. * * @hide */ public void setCallerPackageName(@NonNull String callerPackageName) { - this.mCallerPackageName = callerPackageName; - } - - private SearchRequest(Builder b) { - mQuery = requireNonNull(b.mQuery); - mResultOffset = b.mResultOffset; - mResultNumber = b.mResultNumber; - mMaxLatencyMillis = b.mMaxLatencyMillis; - mSearchConstraints = requireNonNull(b.mSearchConstraints); - mCallerPackageName = requireNonNull(b.mCallerPackageName); } /** * @see Creator - * */ @NonNull public static final Creator<SearchRequest> CREATOR = new Creator<SearchRequest>() { @Override public SearchRequest createFromParcel(Parcel p) { - return new SearchRequest(p); + return new SearchRequest(); } @Override @@ -201,13 +130,6 @@ public final class SearchRequest implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(this.mQuery); - dest.writeInt(this.mResultOffset); - dest.writeInt(this.mResultNumber); - dest.writeFloat(this.mMaxLatencyMillis); - dest.writeBundle(this.mSearchConstraints); - dest.writeString(getRequestId()); - dest.writeString(this.mCallerPackageName); } @Override @@ -217,44 +139,17 @@ public final class SearchRequest implements Parcelable { @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - SearchRequest that = (SearchRequest) obj; - return Objects.equals(mQuery, that.mQuery) - && mResultOffset == that.mResultOffset - && mResultNumber == that.mResultNumber - && mMaxLatencyMillis == that.mMaxLatencyMillis - && Objects.equals(mSearchConstraints, that.mSearchConstraints) - && Objects.equals(mCallerPackageName, that.mCallerPackageName); + return false; } @Override public String toString() { - boolean isPresubmit = - mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION) - && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION); - - String searchProvider = "EMPTY"; - if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) { - searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER); - } - - return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;" - + "is_presubmit:%b;search_provider:%s;callerPackageName:%s}", mQuery, - mResultOffset, mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, - mCallerPackageName); + return ""; } @Override public int hashCode() { - return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints, mCallerPackageName); + return 0; } /** @@ -264,87 +159,62 @@ public final class SearchRequest implements Parcelable { */ @SystemApi public static final class Builder { - private String mQuery; - private int mResultOffset; - private int mResultNumber; - private float mMaxLatencyMillis; - private Bundle mSearchConstraints; - private String mCallerPackageName; - /** - * * @param query the query for search. - * * @hide */ @SystemApi public Builder(@NonNull String query) { - mQuery = query; - - mResultOffset = 0; - mResultNumber = 10; - mMaxLatencyMillis = 200; - mSearchConstraints = Bundle.EMPTY; - mCallerPackageName = "DEFAULT_CALLER"; } /** Sets the input query. */ @NonNull public Builder setQuery(@NonNull String query) { - this.mQuery = query; return this; } /** Sets the search result offset. */ @NonNull public Builder setResultOffset(int resultOffset) { - this.mResultOffset = resultOffset; return this; } /** Sets the expected number of search result. */ @NonNull public Builder setResultNumber(int resultNumber) { - this.mResultNumber = resultNumber; return this; } /** Sets the maximum acceptable search latency. */ @NonNull public Builder setMaxLatencyMillis(float maxLatencyMillis) { - this.mMaxLatencyMillis = maxLatencyMillis; return this; } - /** Sets the search constraints, such as the user location, the search type(presubmit or - * postsubmit), and the target search providers. */ + /** + * Sets the search constraints, such as the user location, the search type(presubmit or + * postsubmit), and the target search providers. + */ @NonNull public Builder setSearchConstraints(@Nullable Bundle searchConstraints) { - this.mSearchConstraints = searchConstraints; return this; } - /** Sets the caller, and this will be set by the system server. + /** + * Sets the caller, and this will be set by the system server. * * @hide */ @NonNull @TestApi public Builder setCallerPackageName(@NonNull String callerPackageName) { - this.mCallerPackageName = callerPackageName; return this; } /** Builds a SearchRequest based-on the given params. */ @NonNull public SearchRequest build() { - if (mQuery == null || mResultOffset < 0 || mResultNumber < 1 || mMaxLatencyMillis < 0 - || mSearchConstraints == null) { - throw new IllegalStateException("Please make sure all required args are valid."); - } - - return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints, mCallerPackageName); + return new SearchRequest(); } } } diff --git a/core/java/android/app/cloudsearch/SearchResponse.aidl b/core/java/android/app/cloudsearch/SearchResponse.aidl deleted file mode 100644 index 2064d112c6b9..000000000000 --- a/core/java/android/app/cloudsearch/SearchResponse.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2022, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.cloudsearch; - -parcelable SearchResponse;
\ No newline at end of file diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java index 607bd561d331..c86142e0d22d 100644 --- a/core/java/android/app/cloudsearch/SearchResponse.java +++ b/core/java/android/app/cloudsearch/SearchResponse.java @@ -15,8 +15,6 @@ */ package android.app.cloudsearch; -import static java.util.Objects.requireNonNull; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -25,7 +23,6 @@ import android.os.Parcelable; import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * A {@link SearchResponse} includes search results and associated meta information. @@ -37,77 +34,53 @@ public final class SearchResponse implements Parcelable { /** @hide */ @IntDef(prefix = {"SEARCH_STATUS_"}, value = {SEARCH_STATUS_UNKNOWN, - SEARCH_STATUS_OK, - SEARCH_STATUS_TIME_OUT, - SEARCH_STATUS_NO_INTERNET}) - public @interface SearchStatusCode {} + SEARCH_STATUS_OK, + SEARCH_STATUS_TIME_OUT, + SEARCH_STATUS_NO_INTERNET}) + public @interface SearchStatusCode { + } + public static final int SEARCH_STATUS_UNKNOWN = -1; public static final int SEARCH_STATUS_OK = 0; public static final int SEARCH_STATUS_TIME_OUT = 1; public static final int SEARCH_STATUS_NO_INTERNET = 2; - private final int mStatusCode; - - /** Auto set by system servier, and the provider cannot set it. */ - @NonNull - private String mSource; - - @NonNull - private final List<SearchResult> mSearchResults; - - private SearchResponse(Parcel in) { - this.mStatusCode = in.readInt(); - this.mSource = in.readString(); - this.mSearchResults = in.createTypedArrayList(SearchResult.CREATOR); - } - - private SearchResponse(@SearchStatusCode int statusCode, String source, - List<SearchResult> searchResults) { - mStatusCode = statusCode; - mSource = source; - mSearchResults = searchResults; + private SearchResponse() { } /** Gets the search status code. */ public int getStatusCode() { - return mStatusCode; + return SEARCH_STATUS_UNKNOWN; } /** Gets the search provider package name. */ @NonNull public String getSource() { - return mSource; + return ""; } /** Gets the search results, which can be empty. */ @NonNull public List<SearchResult> getSearchResults() { - return mSearchResults; + return new ArrayList<SearchResult>(); } - /** Sets the search provider, and this will be set by the system server. + /** + * Sets the search provider, and this will be set by the system server. * * @hide */ public void setSource(@NonNull String source) { - this.mSource = source; - } - - private SearchResponse(Builder b) { - mStatusCode = b.mStatusCode; - mSource = requireNonNull(b.mSource); - mSearchResults = requireNonNull(b.mSearchResults); } /** - * * @see Creator - * */ - @NonNull public static final Creator<SearchResponse> CREATOR = new Creator<SearchResponse>() { + @NonNull + public static final Creator<SearchResponse> CREATOR = new Creator<SearchResponse>() { @Override public SearchResponse createFromParcel(Parcel p) { - return new SearchResponse(p); + return new SearchResponse(); } @Override @@ -118,9 +91,6 @@ public final class SearchResponse implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(this.mStatusCode); - dest.writeString(this.mSource); - dest.writeTypedList(this.mSearchResults); } @Override @@ -130,23 +100,12 @@ public final class SearchResponse implements Parcelable { @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - SearchResponse that = (SearchResponse) obj; - return mStatusCode == that.mStatusCode - && Objects.equals(mSource, that.mSource) - && Objects.equals(mSearchResults, that.mSearchResults); + return false; } @Override public int hashCode() { - return Objects.hash(mStatusCode, mSource, mSearchResults); + return 0; } /** @@ -156,59 +115,40 @@ public final class SearchResponse implements Parcelable { */ @SystemApi public static final class Builder { - private int mStatusCode; - private String mSource; - private List<SearchResult> mSearchResults; - /** - * * @param statusCode the search status code. - * * @hide */ @SystemApi public Builder(@SearchStatusCode int statusCode) { - mStatusCode = statusCode; - - /** Init with a default value. */ - mSource = "DEFAULT"; - - mSearchResults = new ArrayList<SearchResult>(); } /** Sets the search status code. */ @NonNull public Builder setStatusCode(@SearchStatusCode int statusCode) { - this.mStatusCode = statusCode; return this; } - /** Sets the search provider, and this will be set by the system server. + /** + * Sets the search provider, and this will be set by the system server. * * @hide */ @NonNull public Builder setSource(@NonNull String source) { - this.mSource = source; return this; } /** Sets the search results. */ @NonNull public Builder setSearchResults(@NonNull List<SearchResult> searchResults) { - this.mSearchResults = searchResults; return this; } /** Builds a SearchResponse based-on the given parameters. */ @NonNull public SearchResponse build() { - if (mStatusCode < SEARCH_STATUS_UNKNOWN || mStatusCode > SEARCH_STATUS_NO_INTERNET - || mSearchResults == null) { - throw new IllegalStateException("Please make sure all @NonNull args are assigned."); - } - - return new SearchResponse(mStatusCode, mSource, mSearchResults); + return new SearchResponse(); } } } diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/app/cloudsearch/SearchResult.aidl deleted file mode 100644 index daebfbf3307f..000000000000 --- a/core/java/android/app/cloudsearch/SearchResult.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2022, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.cloudsearch; - -parcelable SearchResult;
\ No newline at end of file diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java index c6583b65f9c2..123c3a2a5f48 100644 --- a/core/java/android/app/cloudsearch/SearchResult.java +++ b/core/java/android/app/cloudsearch/SearchResult.java @@ -15,8 +15,6 @@ */ package android.app.cloudsearch; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.StringDef; import android.annotation.SuppressLint; @@ -27,7 +25,6 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Objects; /** * A {@link SearchResult} includes all the information for one result item. @@ -37,17 +34,6 @@ import java.util.Objects; @SystemApi public final class SearchResult implements Parcelable { - /** Short content best describing the result item. */ - @NonNull - private final String mTitle; - - /** Matched contents in the result item. */ - @NonNull - private final String mSnippet; - - /** Ranking Score provided by the search provider. */ - private final float mScore; - /** * List of public static KEYS for Bundles in mExtraInfos. * mExtraInfos contains various information specified for different data types. @@ -56,28 +42,30 @@ public final class SearchResult implements Parcelable { */ @Retention(RetentionPolicy.SOURCE) @StringDef(prefix = {"EXTRAINFO_"}, - value = {EXTRAINFO_APP_DOMAIN_URL, - EXTRAINFO_APP_ICON, - EXTRAINFO_APP_DEVELOPER_NAME, - EXTRAINFO_APP_SIZE_BYTES, - EXTRAINFO_APP_STAR_RATING, - EXTRAINFO_APP_IARC, - EXTRAINFO_APP_REVIEW_COUNT, - EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER, - EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER, - EXTRAINFO_SHORT_DESCRIPTION, - EXTRAINFO_LONG_DESCRIPTION, - EXTRAINFO_SCREENSHOTS, - EXTRAINFO_APP_BADGES, - EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING, - EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING, - EXTRAINFO_ACTION_APP_CARD, - EXTRAINFO_ACTION_INSTALL_BUTTON, - EXTRAINFO_APP_PACKAGE_NAME, - EXTRAINFO_APP_INSTALL_COUNT, - EXTRAINFO_WEB_URL, - EXTRAINFO_WEB_ICON}) - public @interface SearchResultExtraInfoKey {} + value = {EXTRAINFO_APP_DOMAIN_URL, + EXTRAINFO_APP_ICON, + EXTRAINFO_APP_DEVELOPER_NAME, + EXTRAINFO_APP_SIZE_BYTES, + EXTRAINFO_APP_STAR_RATING, + EXTRAINFO_APP_IARC, + EXTRAINFO_APP_REVIEW_COUNT, + EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER, + EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER, + EXTRAINFO_SHORT_DESCRIPTION, + EXTRAINFO_LONG_DESCRIPTION, + EXTRAINFO_SCREENSHOTS, + EXTRAINFO_APP_BADGES, + EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING, + EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING, + EXTRAINFO_ACTION_APP_CARD, + EXTRAINFO_ACTION_INSTALL_BUTTON, + EXTRAINFO_APP_PACKAGE_NAME, + EXTRAINFO_APP_INSTALL_COUNT, + EXTRAINFO_WEB_URL, + EXTRAINFO_WEB_ICON}) + public @interface SearchResultExtraInfoKey { + } + /** This App developer website's domain URL, String value expected. */ public static final String EXTRAINFO_APP_DOMAIN_URL = "android.app.cloudsearch.APP_DOMAIN_URL"; /** This App icon, android.graphics.drawable.Icon expected. */ @@ -90,7 +78,8 @@ public final class SearchResult implements Parcelable { /** This App developer's name, Double value expected. */ public static final String EXTRAINFO_APP_STAR_RATING = "android.app.cloudsearch.APP_STAR_RATING"; - /** This App's IARC rating, String value expected. + /** + * This App's IARC rating, String value expected. * IARC (International Age Rating Coalition) is partnered globally with major * content rating organizations to provide a centralized and one-stop-shop for * rating content on a global scale. @@ -142,62 +131,40 @@ public final class SearchResult implements Parcelable { /** Web content's domain icon, android.graphics.drawable.Icon expected. */ public static final String EXTRAINFO_WEB_ICON = "android.app.cloudsearch.WEB_ICON"; - @NonNull - private Bundle mExtraInfos; - - private SearchResult(Parcel in) { - this.mTitle = in.readString(); - this.mSnippet = in.readString(); - this.mScore = in.readFloat(); - this.mExtraInfos = in.readBundle(); - } - - private SearchResult(String title, String snippet, float score, Bundle extraInfos) { - mTitle = title; - mSnippet = snippet; - mScore = score; - mExtraInfos = extraInfos; + private SearchResult() { } /** Gets the search result title. */ @NonNull public String getTitle() { - return mTitle; + return ""; } /** Gets the search result snippet. */ @NonNull public String getSnippet() { - return mSnippet; + return ""; } /** Gets the ranking score provided by the original search provider. */ public float getScore() { - return mScore; + return 0; } /** Gets the extra information associated with the search result. */ @NonNull public Bundle getExtraInfos() { - return mExtraInfos; - } - - private SearchResult(Builder b) { - mTitle = requireNonNull(b.mTitle); - mSnippet = requireNonNull(b.mSnippet); - mScore = b.mScore; - mExtraInfos = requireNonNull(b.mExtraInfos); + return Bundle.EMPTY; } /** - * * @see Creator - * */ - @NonNull public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() { + @NonNull + public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() { @Override public SearchResult createFromParcel(Parcel p) { - return new SearchResult(p); + return new SearchResult(); } @Override @@ -208,10 +175,6 @@ public final class SearchResult implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(this.mTitle); - dest.writeString(this.mSnippet); - dest.writeFloat(this.mScore); - dest.writeBundle(this.mExtraInfos); } @Override @@ -221,24 +184,12 @@ public final class SearchResult implements Parcelable { @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - SearchResult that = (SearchResult) obj; - return Objects.equals(mTitle, that.mTitle) - && Objects.equals(mSnippet, that.mSnippet) - && mScore == that.mScore - && Objects.equals(mExtraInfos, that.mExtraInfos); + return false; } @Override public int hashCode() { - return Objects.hash(mTitle, mSnippet, mScore, mExtraInfos); + return 0; } /** @@ -248,63 +199,43 @@ public final class SearchResult implements Parcelable { */ @SystemApi public static final class Builder { - private String mTitle; - private String mSnippet; - private float mScore; - private Bundle mExtraInfos; - /** - * - * @param title the title to the search result. + * @param title the title to the search result. * @param extraInfos the extra infos associated with the search result. - * * @hide */ @SystemApi public Builder(@NonNull String title, @NonNull Bundle extraInfos) { - mTitle = title; - mExtraInfos = extraInfos; - - mSnippet = ""; - mScore = 0; } /** Sets the title to the search result. */ @NonNull public Builder setTitle(@NonNull String title) { - this.mTitle = title; return this; } /** Sets the snippet to the search result. */ @NonNull public Builder setSnippet(@NonNull String snippet) { - this.mSnippet = snippet; return this; } /** Sets the ranking score to the search result. */ @NonNull public Builder setScore(float score) { - this.mScore = score; return this; } /** Adds extra information to the search result for rendering in the UI. */ @NonNull public Builder setExtraInfos(@NonNull Bundle extraInfos) { - this.mExtraInfos = extraInfos; return this; } /** Builds a SearchResult based-on the given parameters. */ @NonNull public SearchResult build() { - if (mTitle == null || mExtraInfos == null || mSnippet == null) { - throw new IllegalStateException("Please make sure all required args are assigned."); - } - - return new SearchResult(mTitle, mSnippet, mScore, mExtraInfos); + return new SearchResult(); } } } diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS index f9d3222ada2a..b95f8bb72218 100644 --- a/core/java/android/content/pm/OWNERS +++ b/core/java/android/content/pm/OWNERS @@ -1,11 +1,10 @@ # Bug component: 36137 -toddke@android.com -toddke@google.com patb@google.com +per-file Package* = file:/PACKAGE_MANAGER_OWNERS per-file PackageParser.java = set noparent -per-file PackageParser.java = chiuwinson@google.com,patb@google.com,toddke@google.com +per-file PackageParser.java = chiuwinson@google.com,patb@google.com per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 10d6f2d6d04b..64fed63c7159 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -78,7 +78,6 @@ public class ApkLiteParseUtils { public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; private static final int PARSE_IS_SYSTEM_DIR = 1 << 4; private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; - private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8; private static final String TAG_APPLICATION = "application"; private static final String TAG_PACKAGE_VERIFIER = "package-verifier"; private static final String TAG_PROFILEABLE = "profileable"; @@ -103,7 +102,7 @@ public class ApkLiteParseUtils { public static ParseResult<PackageLite> parsePackageLite(ParseInput input, File packageFile, int flags) { if (packageFile.isDirectory()) { - return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags); + return parseClusterPackageLite(input, packageFile, flags); } else { return parseMonolithicPackageLite(input, packageFile, flags); } @@ -137,38 +136,19 @@ public class ApkLiteParseUtils { /** * Parse lightweight details about a directory of APKs. * - * @param packageDirOrApk is the folder that contains split apks for a regular app or the - * framework-res.apk for framwork-res splits (in which case the - * splits come in the <code>frameworkSplits</code> parameter) + * @param packageDir is the folder that contains split apks for a regular app */ public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input, - File packageDirOrApk, List<File> frameworkSplits, int flags) { + File packageDir, int flags) { final File[] files; - final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0; - if (parsingFrameworkSplits) { - if (ArrayUtils.isEmpty(frameworkSplits)) { - return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, - "No packages found in split"); - } - files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]); - // we also want to process the base apk so add it to the array - files[files.length - 1] = packageDirOrApk; - } else { - files = packageDirOrApk.listFiles(); - if (ArrayUtils.isEmpty(files)) { - return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, - "No packages found in split"); - } - // Apk directory is directly nested under the current directory - if (files.length == 1 && files[0].isDirectory()) { - return parseClusterPackageLite(input, files[0], frameworkSplits, flags); - } + files = packageDir.listFiles(); + if (ArrayUtils.isEmpty(files)) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, + "No packages found in split"); } - - if (parsingFrameworkSplits) { - // disable the flag for checking the certificates of the splits. We know they - // won't match, but we rely on the mainline apex to be safe if it was installed - flags = flags & ~PARSE_COLLECT_CERTIFICATES; + // Apk directory is directly nested under the current directory + if (files.length == 1 && files[0].isDirectory()) { + return parseClusterPackageLite(input, files[0], flags); } String packageName = null; @@ -186,10 +166,6 @@ public class ApkLiteParseUtils { } final ApkLite lite = result.getResult(); - if (parsingFrameworkSplits && file == files[files.length - 1]) { - baseApk = lite; - break; - } // Assert that all package names and version codes are // consistent with the first one we encounter. if (packageName == null) { @@ -201,8 +177,7 @@ public class ApkLiteParseUtils { "Inconsistent package " + lite.getPackageName() + " in " + file + "; expected " + packageName); } - // we allow version codes that do not match for framework splits - if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) { + if (versionCode != lite.getVersionCode()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, "Inconsistent version " + lite.getVersionCode() + " in " + file + "; expected " + versionCode); @@ -217,15 +192,11 @@ public class ApkLiteParseUtils { } } } - // baseApk is set in the last iteration of the for each loop when we are parsing - // frameworkRes splits or needs to be done now otherwise - if (!parsingFrameworkSplits) { - baseApk = apks.remove(null); - } + baseApk = apks.remove(null); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks); + return composePackageLiteFromApks(input, packageDir, baseApk, apks); } /** diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 53dfed374fbc..3915a6ccb46d 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -17,6 +17,7 @@ package android.content.res; import static android.content.res.Resources.ID_NULL; +import static android.system.OsConstants.EINVAL; import android.annotation.AnyRes; import android.annotation.NonNull; @@ -28,6 +29,7 @@ import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; +import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import org.xmlpull.v1.XmlPullParserException; @@ -92,6 +94,16 @@ public final class XmlBlock implements AutoCloseable { } } + /** + * Reference Error.h UNEXPECTED_NULL + */ + private static final int ERROR_NULL_DOCUMENT = Integer.MIN_VALUE + 8; + /** + * The reason not to ResXMLParser::BAD_DOCUMENT which is -1 is that other places use the same + * value. Reference Error.h BAD_VALUE = -EINVAL + */ + private static final int ERROR_BAD_DOCUMENT = -EINVAL; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class Parser implements XmlResourceParser { Parser(long parseState, XmlBlock block) { @@ -168,7 +180,11 @@ public final class XmlBlock implements AutoCloseable { return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } public int getLineNumber() { - return nativeGetLineNumber(mParseState); + final int lineNumber = nativeGetLineNumber(mParseState); + if (lineNumber == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return lineNumber; } public int getEventType() throws XmlPullParserException { return mEventType; @@ -203,7 +219,10 @@ public final class XmlBlock implements AutoCloseable { } @NonNull public String getAttributeNamespace(int index) { - int id = nativeGetAttributeNamespace(mParseState, index); + final int id = nativeGetAttributeNamespace(mParseState, index); + if (id == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id); if (id >= 0) return getSequenceString(mStrings.getSequence(id)); else if (id == -1) return ""; @@ -211,8 +230,11 @@ public final class XmlBlock implements AutoCloseable { } @NonNull public String getAttributeName(int index) { - int id = nativeGetAttributeName(mParseState, index); + final int id = nativeGetAttributeName(mParseState, index); if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id); + if (id == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } if (id >= 0) return getSequenceString(mStrings.getSequence(id)); throw new IndexOutOfBoundsException(String.valueOf(index)); } @@ -224,21 +246,38 @@ public final class XmlBlock implements AutoCloseable { return false; } public int getAttributeCount() { - return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1; + if (mEventType == START_TAG) { + final int count = nativeGetAttributeCount(mParseState); + if (count == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return count; + } else { + return -1; + } } @NonNull public String getAttributeValue(int index) { - int id = nativeGetAttributeStringValue(mParseState, index); + final int id = nativeGetAttributeStringValue(mParseState, index); + if (id == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id); if (id >= 0) return getSequenceString(mStrings.getSequence(id)); // May be some other type... check and try to convert if so. - int t = nativeGetAttributeDataType(mParseState, index); + final int t = nativeGetAttributeDataType(mParseState, index); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } if (t == TypedValue.TYPE_NULL) { throw new IndexOutOfBoundsException(String.valueOf(index)); } - int v = nativeGetAttributeData(mParseState, index); + final int v = nativeGetAttributeData(mParseState, index); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } return TypedValue.coerceToString(t, v); } public String getAttributeType(int index) { @@ -272,6 +311,9 @@ public final class XmlBlock implements AutoCloseable { return END_DOCUMENT; } int ev = nativeNext(mParseState); + if (ev == ERROR_BAD_DOCUMENT) { + throw new XmlPullParserException("Corrupt XML binary file"); + } if (mDecNextDepth) { mDepth--; mDecNextDepth = false; @@ -338,7 +380,11 @@ public final class XmlBlock implements AutoCloseable { } public int getAttributeNameResource(int index) { - return nativeGetAttributeResource(mParseState, index); + final int resourceNameId = nativeGetAttributeResource(mParseState, index); + if (resourceNameId == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return resourceNameId; } public int getAttributeListValue(String namespace, String attribute, @@ -393,8 +439,14 @@ public final class XmlBlock implements AutoCloseable { public int getAttributeListValue(int idx, String[] options, int defaultValue) { - int t = nativeGetAttributeDataType(mParseState, idx); - int v = nativeGetAttributeData(mParseState, idx); + final int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } if (t == TypedValue.TYPE_STRING) { return XmlUtils.convertValueToList( mStrings.getSequence(v), options, defaultValue); @@ -403,62 +455,99 @@ public final class XmlBlock implements AutoCloseable { } public boolean getAttributeBooleanValue(int idx, boolean defaultValue) { - int t = nativeGetAttributeDataType(mParseState, idx); + final int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. - if (t >= TypedValue.TYPE_FIRST_INT && - t <= TypedValue.TYPE_LAST_INT) { - return nativeGetAttributeData(mParseState, idx) != 0; + if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return v != 0; } return defaultValue; } public int getAttributeResourceValue(int idx, int defaultValue) { - int t = nativeGetAttributeDataType(mParseState, idx); + final int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_REFERENCE) { - return nativeGetAttributeData(mParseState, idx); + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return v; } return defaultValue; } public int getAttributeIntValue(int idx, int defaultValue) { - int t = nativeGetAttributeDataType(mParseState, idx); + final int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. - if (t >= TypedValue.TYPE_FIRST_INT && - t <= TypedValue.TYPE_LAST_INT) { - return nativeGetAttributeData(mParseState, idx); + if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return v; } return defaultValue; } public int getAttributeUnsignedIntValue(int idx, int defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. - if (t >= TypedValue.TYPE_FIRST_INT && - t <= TypedValue.TYPE_LAST_INT) { - return nativeGetAttributeData(mParseState, idx); + if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return v; } return defaultValue; } public float getAttributeFloatValue(int idx, float defaultValue) { - int t = nativeGetAttributeDataType(mParseState, idx); + final int t = nativeGetAttributeDataType(mParseState, idx); + if (t == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_FLOAT) { - return Float.intBitsToFloat( - nativeGetAttributeData(mParseState, idx)); + final int v = nativeGetAttributeData(mParseState, idx); + if (v == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return Float.intBitsToFloat(v); } throw new RuntimeException("not a float!"); } @Nullable public String getIdAttribute() { - int id = nativeGetIdAttribute(mParseState); + final int id = nativeGetIdAttribute(mParseState); + if (id == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } @Nullable public String getClassAttribute() { - int id = nativeGetClassAttribute(mParseState); + final int id = nativeGetClassAttribute(mParseState); + if (id == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } @@ -468,7 +557,11 @@ public final class XmlBlock implements AutoCloseable { } public int getStyleAttribute() { - return nativeGetStyleAttribute(mParseState); + final int styleAttributeId = nativeGetStyleAttribute(mParseState); + if (styleAttributeId == ERROR_NULL_DOCUMENT) { + throw new NullPointerException("Null document"); + } + return styleAttributeId; } private String getSequenceString(@Nullable CharSequence str) { @@ -544,37 +637,55 @@ public final class XmlBlock implements AutoCloseable { // ----------- @FastNative ------------------ @FastNative + private static native int nativeGetAttributeIndex( + long state, String namespace, String name); + + // ----------- @CriticalNative ------------------ + @CriticalNative /*package*/ static final native int nativeNext(long state); - @FastNative + + @CriticalNative private static final native int nativeGetNamespace(long state); - @FastNative + + @CriticalNative /*package*/ static final native int nativeGetName(long state); - @FastNative + + @CriticalNative private static final native int nativeGetText(long state); - @FastNative + + @CriticalNative private static final native int nativeGetLineNumber(long state); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeCount(long state); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeNamespace(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeName(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeResource(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeDataType(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeData(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetAttributeStringValue(long state, int idx); - @FastNative + + @CriticalNative private static final native int nativeGetIdAttribute(long state); - @FastNative + + @CriticalNative private static final native int nativeGetClassAttribute(long state); - @FastNative + + @CriticalNative private static final native int nativeGetStyleAttribute(long state); - @FastNative - private static final native int nativeGetAttributeIndex(long state, String namespace, String name); - @FastNative + + @CriticalNative private static final native int nativeGetSourceResId(long state); } diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index 30aa4db938da..bdd45e6df448 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -16,6 +16,7 @@ package android.hardware.devicestate; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; @@ -115,6 +116,52 @@ public final class DeviceStateManager { } /** + * Submits a {@link DeviceStateRequest request} to override the base state of the device. This + * should only be used for testing, where you want to simulate the physical change to the + * device state. + * <p> + * By default, the request is kept active until one of the following occurs: + * <ul> + * <li>The physical state of the device changes</li> + * <li>The system deems the request can no longer be honored, for example if the requested + * state becomes unsupported. + * <li>A call to {@link #cancelBaseStateOverride}. + * <li>Another processes submits a request succeeding this request in which case the request + * will be canceled. + * </ul> + * + * Submitting a base state override request may not cause any change in the presentation + * of the system if there is an emulated request made through {@link #requestState}, as the + * emulated override requests take priority. + * + * @throws IllegalArgumentException if the requested state is unsupported. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission. + * + * @see DeviceStateRequest + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void requestBaseStateOverride(@NonNull DeviceStateRequest request, + @Nullable @CallbackExecutor Executor executor, + @Nullable DeviceStateRequest.Callback callback) { + mGlobal.requestBaseStateOverride(request, executor, callback); + } + + /** + * Cancels the active {@link DeviceStateRequest} previously submitted with a call to + * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * <p> + * This method is noop if there is no base state request currently active. + * + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE) + public void cancelBaseStateOverride() { + mGlobal.cancelBaseStateOverride(); + } + + /** * Registers a callback to receive notifications about changes in device state. * * @param executor the executor to process notifications. diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index aba538f51043..738045dafdf1 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -18,6 +18,7 @@ package android.hardware.devicestate; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.Binder; @@ -81,6 +82,7 @@ public final class DeviceStateManagerGlobal { @VisibleForTesting public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) { mDeviceStateManager = deviceStateManager; + registerCallbackIfNeededLocked(); } /** @@ -116,27 +118,22 @@ public final class DeviceStateManagerGlobal { * DeviceStateRequest.Callback) * @see DeviceStateRequest */ + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void requestState(@NonNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { - if (callback == null && executor != null) { - throw new IllegalArgumentException("Callback must be supplied with executor."); - } else if (executor == null && callback != null) { - throw new IllegalArgumentException("Executor must be supplied with callback."); - } - + DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, + executor); synchronized (mLock) { - registerCallbackIfNeededLocked(); - if (findRequestTokenLocked(request) != null) { // This request has already been submitted. return; } - // Add the request wrapper to the mRequests array before requesting the state as the // callback could be triggered immediately if the mDeviceStateManager IBinder is in the // same process as this instance. IBinder token = new Binder(); - mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor)); + mRequests.put(token, requestWrapper); try { mDeviceStateManager.requestState(token, request.getState(), request.getFlags()); @@ -153,10 +150,10 @@ public final class DeviceStateManagerGlobal { * * @see DeviceStateManager#cancelStateRequest */ + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void cancelStateRequest() { synchronized (mLock) { - registerCallbackIfNeededLocked(); - try { mDeviceStateManager.cancelStateRequest(); } catch (RemoteException ex) { @@ -166,6 +163,56 @@ public final class DeviceStateManagerGlobal { } /** + * Submits a {@link DeviceStateRequest request} to modify the base state of the device. + * + * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor, + * DeviceStateRequest.Callback) + * @see DeviceStateRequest + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void requestBaseStateOverride(@NonNull DeviceStateRequest request, + @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { + DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, + executor); + synchronized (mLock) { + if (findRequestTokenLocked(request) != null) { + // This request has already been submitted. + return; + } + // Add the request wrapper to the mRequests array before requesting the state as the + // callback could be triggered immediately if the mDeviceStateManager IBinder is in the + // same process as this instance. + IBinder token = new Binder(); + mRequests.put(token, requestWrapper); + + try { + mDeviceStateManager.requestBaseStateOverride(token, request.getState(), + request.getFlags()); + } catch (RemoteException ex) { + mRequests.remove(token); + throw ex.rethrowFromSystemServer(); + } + } + } + + /** + * Cancels a {@link DeviceStateRequest request} previously submitted with a call to + * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * + * @see DeviceStateManager#cancelBaseStateOverride + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void cancelBaseStateOverride() { + synchronized (mLock) { + try { + mDeviceStateManager.cancelBaseStateOverride(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + /** * Registers a callback to receive notifications about changes in device state. * * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback) @@ -179,9 +226,6 @@ public final class DeviceStateManagerGlobal { // This callback is already registered. return; } - - registerCallbackIfNeededLocked(); - // Add the callback wrapper to the mCallbacks array after registering the callback as // the callback could be triggered immediately if the mDeviceStateManager IBinder is in // the same process as this instance. @@ -357,6 +401,8 @@ public final class DeviceStateManagerGlobal { DeviceStateRequestWrapper(@NonNull DeviceStateRequest request, @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + validateRequestWrapperParameters(callback, executor); + mRequest = request; mCallback = callback; mExecutor = executor; @@ -377,5 +423,14 @@ public final class DeviceStateManagerGlobal { mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest)); } + + private void validateRequestWrapperParameters( + @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + if (callback == null && executor != null) { + throw new IllegalArgumentException("Callback must be supplied with executor."); + } else if (executor == null && callback != null) { + throw new IllegalArgumentException("Executor must be supplied with callback."); + } + } } } diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index e450e42497a0..7175eae58a26 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -41,6 +41,10 @@ interface IDeviceStateManager { * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a * call to this method. * + * Requesting a state does not cancel a base state override made through + * {@link #requestBaseStateOverride}, but will still attempt to put the device into the + * supplied {@code state}. + * * @param token the request token provided * @param state the state of device the request is asking for * @param flags any flags that correspond to the request @@ -50,14 +54,53 @@ interface IDeviceStateManager { * @throws IllegalStateException if the supplied {@code token} has already been registered. * @throws IllegalArgumentException if the supplied {@code state} is not supported. */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)") void requestState(IBinder token, int state, int flags); /** * Cancels the active request previously submitted with a call to - * {@link #requestState(IBinder, int, int)}. + * {@link #requestState(IBinder, int, int)}. Will have no effect on any base state override that + * was previously requested with {@link #requestBaseStateOverride}. * * @throws IllegalStateException if a callback has not yet been registered for the calling * process. */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)") void cancelStateRequest(); + + /** + * Requests that the device's base state be overridden to the supplied {@code state}. A callback + * <b>MUST</b> have been previously registered with + * {@link #registerCallback(IDeviceStateManagerCallback)} before a call to this method. + * + * This method should only be used for testing, when you want to simulate the device physically + * changing states. If you are looking to change device state for a feature, where the system + * should still be aware that the physical state is different than the emulated state, use + * {@link #requestState}. + * + * @param token the request token provided + * @param state the state of device the request is asking for + * @param flags any flags that correspond to the request + * + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. + * @throws IllegalStateException if the supplied {@code token} has already been registered. + * @throws IllegalArgumentException if the supplied {@code state} is not supported. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") + void requestBaseStateOverride(IBinder token, int state, int flags); + + /** + * Cancels the active base state request previously submitted with a call to + * {@link #overrideBaseState(IBinder, int, int)}. + * + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") + void cancelBaseStateOverride(); } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 8bc11cbc61de..dfb42365357d 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -855,6 +855,16 @@ public final class DisplayManager { return mGlobal.getUserDisabledHdrTypes(); } + /** + * Overrides HDR modes for a display device. + * + * @hide + */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) + @TestApi + public void overrideHdrTypes(int displayId, @NonNull int[] modes) { + mGlobal.overrideHdrTypes(displayId, modes); + } /** * Creates a virtual display. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index e2f8592ad329..9294dea50b0d 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -20,9 +20,11 @@ package android.hardware.display; import static android.hardware.display.DisplayManager.EventsMask; import static android.view.Display.HdrCapabilities.HdrType; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -577,6 +579,20 @@ public final class DisplayManagerGlobal { } } + /** + * Overrides HDR modes for a display device. + * + */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) + public void overrideHdrTypes(int displayId, int[] modes) { + try { + mDm.overrideHdrTypes(displayId, modes); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + public void requestColorMode(int displayId, int colorMode) { try { mDm.requestColorMode(displayId, colorMode); diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index fa3c4506d6e9..b166e215075a 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -83,6 +83,9 @@ interface IDisplayManager { // No permissions required. int[] getUserDisabledHdrTypes(); + // Requires ACCESS_SURFACE_FLINGER permission. + void overrideHdrTypes(int displayId, in int[] modes); + // Requires CONFIGURE_DISPLAY_COLOR_MODE void requestColorMode(int displayId, int colorMode); diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index aa5480abafb4..4a18333aee9f 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -16,12 +16,13 @@ package android.hardware.radio; -import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Bitmap; import android.os.RemoteException; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.util.List; import java.util.Map; import java.util.Objects; @@ -29,28 +30,36 @@ import java.util.Objects; /** * Implements the RadioTuner interface by forwarding calls to radio service. */ -class TunerAdapter extends RadioTuner { +final class TunerAdapter extends RadioTuner { private static final String TAG = "BroadcastRadio.TunerAdapter"; - @NonNull private final ITuner mTuner; - @NonNull private final TunerCallbackAdapter mCallback; - private boolean mIsClosed = false; + private final ITuner mTuner; + private final TunerCallbackAdapter mCallback; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private boolean mIsClosed; - private @RadioManager.Band int mBand; + @GuardedBy("mLock") + @RadioManager.Band + private int mBand; + @GuardedBy("mLock") private ProgramList mLegacyListProxy; + + @GuardedBy("mLock") private Map<String, String> mLegacyListFilter; - TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback, + TunerAdapter(ITuner tuner, TunerCallbackAdapter callback, @RadioManager.Band int band) { - mTuner = Objects.requireNonNull(tuner); - mCallback = Objects.requireNonNull(callback); + mTuner = Objects.requireNonNull(tuner, "Tuner cannot be null"); + mCallback = Objects.requireNonNull(callback, "Callback cannot be null"); mBand = band; } @Override public void close() { - synchronized (mTuner) { + synchronized (mLock) { if (mIsClosed) { Log.v(TAG, "Tuner is already closed"); return; @@ -60,8 +69,8 @@ class TunerAdapter extends RadioTuner { mLegacyListProxy.close(); mLegacyListProxy = null; } - mCallback.close(); } + mCallback.close(); try { mTuner.close(); } catch (RemoteException e) { @@ -71,16 +80,20 @@ class TunerAdapter extends RadioTuner { @Override public int setConfiguration(RadioManager.BandConfig config) { - if (config == null) return RadioManager.STATUS_BAD_VALUE; + if (config == null) { + return RadioManager.STATUS_BAD_VALUE; + } try { mTuner.setConfiguration(config); - mBand = config.getType(); + synchronized (mLock) { + mBand = config.getType(); + } return RadioManager.STATUS_OK; } catch (IllegalArgumentException e) { Log.e(TAG, "Can't set configuration", e); return RadioManager.STATUS_BAD_VALUE; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } } @@ -94,7 +107,7 @@ class TunerAdapter extends RadioTuner { config[0] = mTuner.getConfiguration(); return RadioManager.STATUS_OK; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } } @@ -107,7 +120,7 @@ class TunerAdapter extends RadioTuner { Log.e(TAG, "Can't set muted", e); return RadioManager.STATUS_ERROR; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; @@ -118,7 +131,7 @@ class TunerAdapter extends RadioTuner { try { return mTuner.isMuted(); } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return true; } } @@ -126,12 +139,13 @@ class TunerAdapter extends RadioTuner { @Override public int step(int direction, boolean skipSubChannel) { try { - mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); + mTuner.step(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, + skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't step", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; @@ -140,12 +154,13 @@ class TunerAdapter extends RadioTuner { @Override public int scan(int direction, boolean skipSubChannel) { try { - mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); + mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, + skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't scan", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; @@ -154,7 +169,11 @@ class TunerAdapter extends RadioTuner { @Override public int tune(int channel, int subChannel) { try { - mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel)); + int band; + synchronized (mLock) { + band = mBand; + } + mTuner.tune(ProgramSelector.createAmFmSelector(band, channel, subChannel)); } catch (IllegalStateException e) { Log.e(TAG, "Can't tune", e); return RadioManager.STATUS_INVALID_OPERATION; @@ -162,18 +181,18 @@ class TunerAdapter extends RadioTuner { Log.e(TAG, "Can't tune", e); return RadioManager.STATUS_BAD_VALUE; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override - public void tune(@NonNull ProgramSelector selector) { + public void tune(ProgramSelector selector) { try { mTuner.tune(selector); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @@ -185,7 +204,7 @@ class TunerAdapter extends RadioTuner { Log.e(TAG, "Can't cancel", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { - Log.e(TAG, "service died", e); + Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; @@ -196,7 +215,7 @@ class TunerAdapter extends RadioTuner { try { mTuner.cancelAnnouncement(); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @@ -217,11 +236,12 @@ class TunerAdapter extends RadioTuner { } @Override - public @Nullable Bitmap getMetadataImage(int id) { + @Nullable + public Bitmap getMetadataImage(int id) { try { return mTuner.getImage(id); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @@ -230,66 +250,72 @@ class TunerAdapter extends RadioTuner { try { return mTuner.startBackgroundScan(); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @Override - public @NonNull List<RadioManager.ProgramInfo> + public List<RadioManager.ProgramInfo> getProgramList(@Nullable Map<String, String> vendorFilter) { - synchronized (mTuner) { + synchronized (mLock) { if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) { Log.i(TAG, "Program list filter has changed, requesting new list"); mLegacyListProxy = new ProgramList(); mLegacyListFilter = vendorFilter; - mCallback.clearLastCompleteList(); - mCallback.setProgramListObserver(mLegacyListProxy, () -> { }); - try { - mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); - } catch (RemoteException ex) { - throw new RuntimeException("service died", ex); - } + mCallback.setProgramListObserver(mLegacyListProxy, () -> { + Log.i(TAG, "Empty closeListener in programListObserver"); + }); } + } + try { + mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); + } catch (RemoteException ex) { + throw new RuntimeException("Service died", ex); + } - List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList(); - if (list == null) throw new IllegalStateException("Program list is not ready yet"); - return list; + List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList(); + if (list == null) { + throw new IllegalStateException("Program list is not ready yet"); } + return list; } @Override - public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { - synchronized (mTuner) { + @Nullable + public ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + synchronized (mLock) { if (mLegacyListProxy != null) { mLegacyListProxy.close(); mLegacyListProxy = null; } mLegacyListFilter = null; - - ProgramList list = new ProgramList(); - mCallback.setProgramListObserver(list, () -> { - try { - mTuner.stopProgramListUpdates(); - } catch (IllegalStateException ex) { - // it's fine to not stop updates if tuner is already closed - } catch (RemoteException ex) { - Log.e(TAG, "Couldn't stop program list updates", ex); - } - }); - + } + ProgramList list = new ProgramList(); + mCallback.setProgramListObserver(list, () -> { try { - mTuner.startProgramListUpdates(filter); - } catch (UnsupportedOperationException ex) { - Log.i(TAG, "Program list is not supported with this hardware"); - return null; + mTuner.stopProgramListUpdates(); + } catch (IllegalStateException ex) { + // it's fine to not stop updates if tuner is already closed + Log.e(TAG, "Tuner may already be closed", ex); } catch (RemoteException ex) { - mCallback.setProgramListObserver(null, () -> { }); - throw new RuntimeException("service died", ex); + Log.e(TAG, "Couldn't stop program list updates", ex); } + }); - return list; + try { + mTuner.startProgramListUpdates(filter); + } catch (UnsupportedOperationException ex) { + Log.i(TAG, "Program list is not supported with this hardware"); + return null; + } catch (RemoteException ex) { + mCallback.setProgramListObserver(null, () -> { + Log.i(TAG, "Empty closeListener in programListObserver"); + }); + throw new RuntimeException("Service died", ex); } + + return list; } @Override @@ -315,7 +341,7 @@ class TunerAdapter extends RadioTuner { try { return mTuner.isConfigFlagSupported(flag); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @@ -324,7 +350,7 @@ class TunerAdapter extends RadioTuner { try { return mTuner.isConfigFlagSet(flag); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @@ -333,25 +359,26 @@ class TunerAdapter extends RadioTuner { try { mTuner.setConfigFlag(flag, value); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @Override - public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) { + public Map<String, String> setParameters(Map<String, String> parameters) { try { - return mTuner.setParameters(Objects.requireNonNull(parameters)); + return mTuner.setParameters(Objects.requireNonNull(parameters, + "Parameters cannot be null")); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } @Override - public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) { + public Map<String, String> getParameters(List<String> keys) { try { - return mTuner.getParameters(Objects.requireNonNull(keys)); + return mTuner.getParameters(Objects.requireNonNull(keys, "Keys cannot be null")); } catch (RemoteException e) { - throw new RuntimeException("service died", e); + throw new RuntimeException("Service died", e); } } diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index e3f7001d8740..b9782a87735d 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -16,12 +16,13 @@ package android.hardware.radio; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.util.List; import java.util.Map; import java.util.Objects; @@ -29,23 +30,31 @@ import java.util.Objects; /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. */ -class TunerCallbackAdapter extends ITunerCallback.Stub { +final class TunerCallbackAdapter extends ITunerCallback.Stub { private static final String TAG = "BroadcastRadio.TunerCallbackAdapter"; private final Object mLock = new Object(); - @NonNull private final RadioTuner.Callback mCallback; - @NonNull private final Handler mHandler; + private final RadioTuner.Callback mCallback; + private final Handler mHandler; + @GuardedBy("mLock") @Nullable ProgramList mProgramList; // cache for deprecated methods + @GuardedBy("mLock") boolean mIsAntennaConnected = true; + + @GuardedBy("mLock") @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; - private boolean mDelayedCompleteCallback = false; + + @GuardedBy("mLock") + private boolean mDelayedCompleteCallback; + + @GuardedBy("mLock") @Nullable RadioManager.ProgramInfo mCurrentProgramInfo; - TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { - mCallback = callback; + TunerCallbackAdapter(RadioTuner.Callback callback, @Nullable Handler handler) { + mCallback = Objects.requireNonNull(callback, "Callback cannot be null"); if (handler == null) { mHandler = new Handler(Looper.getMainLooper()); } else { @@ -55,31 +64,39 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { void close() { synchronized (mLock) { - if (mProgramList != null) mProgramList.close(); + if (mProgramList != null) { + mProgramList.close(); + } } } void setProgramListObserver(@Nullable ProgramList programList, - @NonNull ProgramList.OnCloseListener closeListener) { - Objects.requireNonNull(closeListener); + ProgramList.OnCloseListener closeListener) { + Objects.requireNonNull(closeListener, "CloseListener cannot be null"); synchronized (mLock) { if (mProgramList != null) { Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); mProgramList.close(); } mProgramList = programList; - if (programList == null) return; + if (programList == null) { + return; + } programList.setOnCloseListener(() -> { synchronized (mLock) { - if (mProgramList != programList) return; + if (mProgramList != programList) { + return; + } mProgramList = null; mLastCompleteList = null; - closeListener.onClose(); } + closeListener.onClose(); }); programList.addOnCompleteListener(() -> { synchronized (mLock) { - if (mProgramList != programList) return; + if (mProgramList != programList) { + return; + } mLastCompleteList = programList.toList(); if (mDelayedCompleteCallback) { Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); @@ -109,7 +126,11 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } boolean isAntennaConnected() { - return mIsAntennaConnected; + boolean isConnected; + synchronized (mLock) { + isConnected = mIsAntennaConnected; + } + return isConnected; } @Override @@ -177,7 +198,9 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { @Override public void onAntennaState(boolean connected) { - mIsAntennaConnected = connected; + synchronized (mLock) { + mIsAntennaConnected = connected; + } mHandler.post(() -> mCallback.onAntennaState(connected)); } @@ -186,6 +209,7 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); } + @GuardedBy("mLock") private void sendBackgroundScanCompleteLocked() { mDelayedCompleteCallback = false; mHandler.post(() -> mCallback.onBackgroundScanComplete()); @@ -213,8 +237,10 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { public void onProgramListUpdated(ProgramList.Chunk chunk) { mHandler.post(() -> { synchronized (mLock) { - if (mProgramList == null) return; - mProgramList.apply(Objects.requireNonNull(chunk)); + if (mProgramList == null) { + return; + } + mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null")); } }); } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index d2e5f9e4ea40..4df013949de5 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -45,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicReferenceArray; /** * Base class for a remotable object, the core part of a lightweight @@ -291,7 +292,7 @@ public class Binder implements IBinder { private IInterface mOwner; @Nullable private String mDescriptor; - private volatile String[] mTransactionTraceNames = null; + private volatile AtomicReferenceArray<String> mTransactionTraceNames = null; private volatile String mSimpleDescriptor = null; private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024; @@ -895,28 +896,32 @@ public class Binder implements IBinder { @VisibleForTesting public final @NonNull String getTransactionTraceName(int transactionCode) { if (mTransactionTraceNames == null) { - final String descriptor = getSimpleDescriptor(); final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT); - final String[] transactionNames = new String[highestId + 1]; - final StringBuffer buf = new StringBuffer(); - for (int i = 0; i <= highestId; i++) { - String transactionName = getTransactionName(i + FIRST_CALL_TRANSACTION); - if (transactionName != null) { - buf.append(descriptor).append(':').append(transactionName); - } else { - buf.append(descriptor).append('#').append(i + FIRST_CALL_TRANSACTION); - } - transactionNames[i] = buf.toString(); - buf.setLength(0); - } - mSimpleDescriptor = descriptor; - mTransactionTraceNames = transactionNames; + mSimpleDescriptor = getSimpleDescriptor(); + mTransactionTraceNames = new AtomicReferenceArray(highestId + 1); } + final int index = transactionCode - FIRST_CALL_TRANSACTION; - if (index < 0 || index >= mTransactionTraceNames.length) { + if (index < 0 || index >= mTransactionTraceNames.length()) { return mSimpleDescriptor + "#" + transactionCode; } - return mTransactionTraceNames[index]; + + String transactionTraceName = mTransactionTraceNames.getAcquire(index); + if (transactionTraceName == null) { + final String transactionName = getTransactionName(transactionCode); + final StringBuffer buf = new StringBuffer(); + + if (transactionName != null) { + buf.append(mSimpleDescriptor).append(":").append(transactionName); + } else { + buf.append(mSimpleDescriptor).append("#").append(transactionCode); + } + + transactionTraceName = buf.toString(); + mTransactionTraceNames.setRelease(index, transactionTraceName); + } + + return transactionTraceName; } private @NonNull String getSimpleDescriptor() { diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 88649cbf9e42..933769a352e9 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -76,7 +76,7 @@ interface IUserManager { String getUserAccount(int userId); void setUserAccount(int userId, String accountName); long getUserCreationTime(int userId); - boolean isUserSwitcherEnabled(int mUserId); + boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId); boolean isRestricted(int userId); boolean canHaveRestrictedProfile(int userId); int getUserSerialNumber(int userId); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 9189c6c8b6c7..a863a87cc2f7 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -3666,9 +3666,6 @@ public final class Parcel { * previously been written via {@link #writeTypedList} with the same object * type. * - * @return A newly created ArrayList containing objects with the same data - * as those that were previously written. - * * @see #writeTypedList */ public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) { diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index f4edcb1317f4..acfd15cb005e 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -21,6 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; +import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -50,6 +51,8 @@ import java.util.ArrayList; */ public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, XmlUtils.WriteMapCallback { + private static final String TAG = "PersistableBundle"; + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */ @@ -118,7 +121,11 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa * @hide */ public PersistableBundle(Bundle b) { - this(b.getItemwiseMap()); + this(b, true); + } + + private PersistableBundle(Bundle b, boolean throwException) { + this(b.getItemwiseMap(), throwException); } /** @@ -127,7 +134,7 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa * @param map a Map containing only those items that can be persisted. * @throws IllegalArgumentException if any element of #map cannot be persisted. */ - private PersistableBundle(ArrayMap<String, Object> map) { + private PersistableBundle(ArrayMap<String, Object> map, boolean throwException) { super(); mFlags = FLAG_DEFUSABLE; @@ -136,16 +143,23 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa // Now verify each item throwing an exception if there is a violation. final int N = mMap.size(); - for (int i=0; i<N; i++) { + for (int i = N - 1; i >= 0; --i) { Object value = mMap.valueAt(i); if (value instanceof ArrayMap) { // Fix up any Maps by replacing them with PersistableBundles. - mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value)); + mMap.setValueAt(i, + new PersistableBundle((ArrayMap<String, Object>) value, throwException)); } else if (value instanceof Bundle) { - mMap.setValueAt(i, new PersistableBundle(((Bundle) value))); + mMap.setValueAt(i, new PersistableBundle((Bundle) value, throwException)); } else if (!isValidType(value)) { - throw new IllegalArgumentException("Bad value in PersistableBundle key=" - + mMap.keyAt(i) + " value=" + value); + final String errorMsg = "Bad value in PersistableBundle key=" + + mMap.keyAt(i) + " value=" + value; + if (throwException) { + throw new IllegalArgumentException(errorMsg); + } else { + Slog.wtfStack(TAG, errorMsg); + mMap.removeAt(i); + } } } } @@ -268,6 +282,15 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa /** @hide */ public void saveToXml(TypedXmlSerializer out) throws IOException, XmlPullParserException { unparcel(); + // Explicitly drop invalid types an attacker may have added before persisting. + for (int i = mMap.size() - 1; i >= 0; --i) { + final Object value = mMap.valueAt(i); + if (!isValidType(value)) { + Slog.e(TAG, "Dropping bad data before persisting: " + + mMap.keyAt(i) + "=" + value); + mMap.removeAt(i); + } + } XmlUtils.writeMapXml(mMap, out, this); } @@ -322,9 +345,12 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { if (event == XmlPullParser.START_TAG) { + // Don't throw an exception when restoring from XML since an attacker could try to + // input invalid data in the persisted file. return new PersistableBundle((ArrayMap<String, Object>) XmlUtils.readThisArrayMapXml(in, startTag, tagName, - new MyReadMapCallback())); + new MyReadMapCallback()), + /* throwException */ false); } } return new PersistableBundle(); // An empty mutable PersistableBundle diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 1e91700fcf01..5c809a1bac1c 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5265,36 +5265,10 @@ public class UserManager { public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) { try { - if (!mService.isUserSwitcherEnabled(mUserId)) { - return false; - } + return mService.isUserSwitcherEnabled(showEvenIfNotActionable, mUserId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } - - // The feature is enabled. But is it worth showing? - return showEvenIfNotActionable - || areThereUsersToWhichToSwitch() // There are switchable users. - || !hasUserRestrictionForUser(DISALLOW_ADD_USER, mUserId); // New users can be added - } - - /** Returns whether there are any users (other than the current user) to which to switch. */ - @RequiresPermission(anyOf = { - android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.CREATE_USERS - }) - private boolean areThereUsersToWhichToSwitch() { - final List<UserInfo> users = getAliveUsers(); - if (users == null) { - return false; - } - int switchableUserCount = 0; - for (UserInfo user : users) { - if (user.supportsSwitchToByUser()) { - ++switchableUserCount; - } - } - return switchableUserCount > 1; } /** diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java index 7edc987ee0c1..fe53d4eeff64 100644 --- a/core/java/android/preference/GenericInflater.java +++ b/core/java/android/preference/GenericInflater.java @@ -406,7 +406,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> { InflateException ie = new InflateException(attrs .getPositionDescription() + ": Error inflating class " - + constructor.getClass().getName()); + + constructor.getDeclaringClass().getName()); ie.initCause(e); throw ie; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3a9af7ef4ed2..cab6acbfe2b7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7462,6 +7462,13 @@ public final class Settings { "trust_agents_initialized"; /** + * Set to 1 by the system after the list of known trust agents have been initialized. + * @hide + */ + public static final String KNOWN_TRUST_AGENTS_INITIALIZED = + "known_trust_agents_initialized"; + + /** * The Logging ID (a unique 64-bit value) as a hex string. * Used as a pseudonymous identifier for logging. * @deprecated This identifier is poorly initialized and has diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 154fcab8827d..194e1b553322 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4316,13 +4316,25 @@ public final class Telephony { * subscription and while is in voice call. * * Default value is empty string. - * + * @deprecated This column is no longer supported. Use + * {@link #COLUMN_ENABLED_MOBILE_DATA_POLICIES} instead. * @hide */ + @Deprecated public static final String COLUMN_DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules"; /** + * TelephonyProvider column name enabled_mobile_data_policies. + * A list of mobile data policies, each of which represented by an integer and joint by ",". + * + * Default value is empty string. + * @hide + */ + public static final String COLUMN_ENABLED_MOBILE_DATA_POLICIES = + "enabled_mobile_data_policies"; + + /** * TelephonyProvider column name for user displayed name. * <P>Type: TEXT (String)</P> * @@ -4820,5 +4832,12 @@ public final class Telephony { * @hide */ public static final String COLUMN_USAGE_SETTING = "usage_setting"; + + /** + * TelephonyProvider column name for user handle associated with this sim. + * + * @hide + */ + public static final String COLUMN_USER_HANDLE = "user_handle"; } } diff --git a/core/java/android/service/cloudsearch/CloudSearchService.java b/core/java/android/service/cloudsearch/CloudSearchService.java index 5efa1acf8ffa..0ce968939e02 100644 --- a/core/java/android/service/cloudsearch/CloudSearchService.java +++ b/core/java/android/service/cloudsearch/CloudSearchService.java @@ -15,25 +15,14 @@ */ package android.service.cloudsearch; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; - import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; -import android.app.cloudsearch.ICloudSearchManager; import android.app.cloudsearch.SearchRequest; import android.app.cloudsearch.SearchResponse; -import android.content.Context; import android.content.Intent; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.service.cloudsearch.ICloudSearchService.Stub; -import android.util.Log; -import android.util.Slog; /** * A service for returning search results from cloud services in response to an on device query. @@ -72,39 +61,18 @@ public abstract class CloudSearchService extends Service { */ public static final String SERVICE_INTERFACE = "android.service.cloudsearch.CloudSearchService"; - private static final boolean DEBUG = false; - private static final String TAG = "CloudSearchService"; - private Handler mHandler; - private ICloudSearchManager mService; - - private final android.service.cloudsearch.ICloudSearchService mInterface = new Stub() { - @Override - public void onSearch(SearchRequest request) { - mHandler.sendMessage( - obtainMessage(CloudSearchService::onSearch, - CloudSearchService.this, request)); - } - }; @CallSuper @Override public void onCreate() { super.onCreate(); - if (DEBUG) { - Log.d(TAG, "onCreate CloudSearchService"); - } - mHandler = new Handler(Looper.getMainLooper(), null, true); - - IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE); - mService = android.app.cloudsearch.ICloudSearchManager.Stub.asInterface(b); } /** * onSearch receives the input request, retrievals the search provider's own * corpus and returns the search response through returnResults below. * - *@param request the search request passed from the client. - * + * @param request the search request passed from the client. */ public abstract void onSearch(@NonNull SearchRequest request); @@ -112,30 +80,16 @@ public abstract class CloudSearchService extends Service { * returnResults returnes the response and its associated requestId, where * requestIs is generated by request through getRequestId(). * - *@param requestId the request ID got from request.getRequestId(). - *@param response the search response returned from the search provider. - * + * @param requestId the request ID got from request.getRequestId(). + * @param response the search response returned from the search provider. */ public final void returnResults(@NonNull String requestId, - @NonNull SearchResponse response) { - try { - mService.returnResults(mInterface.asBinder(), requestId, response); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + @NonNull SearchResponse response) { } @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { - if (DEBUG) { - Log.d(TAG, "onBind CloudSearchService"); - } - if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); - } - Slog.w(TAG, "Tried to bind to wrong intent (should be " - + SERVICE_INTERFACE + ": " + intent); return null; } } diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index 163d6ed4b18b..432444211bce 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.Service; +import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; @@ -36,6 +37,7 @@ public abstract class DreamOverlayService extends Service { private static final String TAG = "DreamOverlayService"; private static final boolean DEBUG = false; private boolean mShowComplications; + private ComponentName mDreamComponent; private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { @Override @@ -56,6 +58,8 @@ public abstract class DreamOverlayService extends Service { public final IBinder onBind(@NonNull Intent intent) { mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, DreamService.DEFAULT_SHOW_COMPLICATIONS); + mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT, + ComponentName.class); return mDreamOverlay.asBinder(); } @@ -84,4 +88,12 @@ public abstract class DreamOverlayService extends Service { public final boolean shouldShowComplications() { return mShowComplications; } + + /** + * Returns the active dream component. + * @hide + */ + public final ComponentName getDreamComponent() { + return mDreamComponent; + } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 6f5212ce2968..13913268cad0 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -218,6 +218,12 @@ public class DreamService extends Service implements Window.Callback { "android.service.dreams.SHOW_COMPLICATIONS"; /** + * Extra containing the component name for the active dream. + * @hide + */ + public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT"; + + /** * The default value for whether to show complications on the overlay. * @hide */ @@ -271,6 +277,7 @@ public class DreamService extends Service implements Window.Callback { overlayIntent.setComponent(overlayService); overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS, fetchShouldShowComplications(context, serviceInfo)); + overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService); context.bindService(overlayIntent, this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE); diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index c6de8438bccb..df69cc00709c 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -88,6 +88,14 @@ public abstract class HotwordDetectionService extends Service { public static final int MAXIMUM_NUMBER_OF_INITIALIZATION_STATUS_CUSTOM_ERROR = 2; /** + * Feature flag for Attention Service. + * + * TODO(b/247920386): Add TestApi annotation + * @hide + */ + public static final boolean ENABLE_PROXIMITY_RESULT = false; + + /** * Indicates that the updated status is successful. */ public static final int INITIALIZATION_STATUS_SUCCESS = 0; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 067946e90361..b2a26fa665f8 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -67,7 +67,6 @@ import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.window.ITaskFpsCallback; -import android.window.ScreenCapture; /** * System private interface to the window manager. @@ -969,11 +968,4 @@ interface IWindowManager * treatment. */ boolean isLetterboxBackgroundMultiColored(); - - /** - * Captures the entire display specified by the displayId using the args provided. If the args - * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured. - */ - oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs, - in ScreenCapture.ScreenCaptureListener listener); } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index ea0012543ba9..5ec796245774 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1457,10 +1457,20 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int CLASSIFICATION_DEEP_PRESS = 2; + /** + * Classification constant: touchpad scroll. + * + * The current event stream represents the user scrolling with two fingers on a touchpad. + * + * @see #getClassification + */ + public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3; + /** @hide */ @Retention(SOURCE) @IntDef(prefix = { "CLASSIFICATION" }, value = { - CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS}) + CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS, + CLASSIFICATION_TWO_FINGER_SWIPE}) public @interface Classification {}; /** @@ -3862,9 +3872,11 @@ public final class MotionEvent extends InputEvent implements Parcelable { return "AMBIGUOUS_GESTURE"; case CLASSIFICATION_DEEP_PRESS: return "DEEP_PRESS"; + case CLASSIFICATION_TWO_FINGER_SWIPE: + return "TWO_FINGER_SWIPE"; } - return "NONE"; + return "UNKNOWN"; } /** diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index fb0cac3ba3f4..2f7ae498b8b1 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -225,8 +225,6 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetDimmingEnabled(long transactionObj, long nativeObject, boolean dimmingEnabled); - private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); - private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject, InputWindowHandle handle); @@ -449,6 +447,7 @@ public final class SurfaceControl implements Parcelable { private String mName; /** + * Note: do not rename, this field is used by native code. * @hide */ public long mNativeObject; @@ -2037,18 +2036,6 @@ public final class SurfaceControl implements Parcelable { } /** - * Overrides HDR modes for a display device. - * - * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security - * Exception. - * @hide - */ - @TestApi - public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) { - nativeOverrideHdrTypes(displayToken, modes); - } - - /** * @hide */ public static long[] getPhysicalDisplayIds() { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 894ce63ca64e..4a59bfb45a0d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8303,7 +8303,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. - if (!isLaidOut()) { + // Sometimes, views are already laid out, but it's still + // not visible to the user, we also do the evaluation once + // the view is visible. ex: There is a fade-in animation + // for the activity, the view will be laid out when the + // animation beginning. On the time, the view is not visible + // to the user. And then as the animation progresses, the view + // becomes visible to the user. + if (!isLaidOut() || !isVisibleToUser()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { if (isFocused()) { @@ -24649,7 +24656,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int viewStateIndex = 0; if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED; if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED; - if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED; + if (isFocused() && hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED; if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED; if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED; if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED; diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java index 8b8359ea5cb2..9f0328909190 100644 --- a/core/java/android/view/inputmethod/InsertGesture.java +++ b/core/java/android/view/inputmethod/InsertGesture.java @@ -53,7 +53,7 @@ public final class InsertGesture extends HandwritingGesture implements Parcelabl } /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/ - @Nullable + @NonNull public String getTextToInsert() { return mTextToInsert; } @@ -62,7 +62,7 @@ public final class InsertGesture extends HandwritingGesture implements Parcelabl * Returns the insertion point {@link PointF} (in screen coordinates) where * {@link #getTextToInsert()} will be inserted. */ - @Nullable + @NonNull public PointF getInsertionPoint() { return mPoint; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 68b902f14079..41d00a267c59 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,6 +195,8 @@ import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; @@ -238,6 +240,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A user interface element that displays text to the user. @@ -1033,6 +1037,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // // End of autofill-related attributes + private Pattern mWhitespacePattern; + /** * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). @@ -9090,6 +9096,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener gestures.add(SelectGesture.class); gestures.add(DeleteGesture.class); gestures.add(InsertGesture.class); + gestures.add(RemoveSpaceGesture.class); + gestures.add(JoinOrSplitGesture.class); outAttrs.setSupportedHandwritingGestures(gestures); return ic; } @@ -9303,7 +9311,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { - int[] range = getRangeForRect(gesture.getSelectionArea(), gesture.getGranularity()); + int[] range = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getSelectionArea()), + gesture.getGranularity()); if (range == null) { return handleGestureFailure(gesture); } @@ -9314,7 +9324,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { - int[] range = getRangeForRect(gesture.getDeletionArea(), gesture.getGranularity()); + int[] range = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getDeletionArea()), + gesture.getGranularity()); if (range == null) { return handleGestureFailure(gesture); } @@ -9326,13 +9338,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { - PointF point = gesture.getInsertionPoint(); - // The coordinates provided are screen coordinates - transform to content coordinates. - int[] screenToViewport = getLocationOnScreen(); - point.offset( - -(screenToViewport[0] + viewportToContentHorizontalOffset()), - -(screenToViewport[1] + viewportToContentVerticalOffset())); - + PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); int line = mLayout.getLineForVertical((int) point.y); if (point.y < mLayout.getLineTop(line) || point.y > mLayout.getLineBottomWithoutSpacing(line)) { @@ -9349,6 +9355,105 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } + /** @hide */ + public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) { + PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint()); + PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); + + // The operation should be applied to the first line of text touched by the line joining + // the points. + int yMin = (int) Math.min(startPoint.y, endPoint.y); + int yMax = (int) Math.max(startPoint.y, endPoint.y); + int line = mLayout.getLineForVertical(yMin); + if (yMax < mLayout.getLineTop(line)) { + // Both points are above the top of the first line. + return handleGestureFailure(gesture); + } + if (yMin > mLayout.getLineBottomWithoutSpacing(line)) { + if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) { + // The points are below the last line, or they are between two lines. + return handleGestureFailure(gesture); + } else { + // Apply the operation to the next line. + line++; + } + } + if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line) + || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) { + return handleGestureFailure(gesture); + } + + // The operation should be applied to all characters touched by the line joining the points. + int startOffset = mLayout.getOffsetForHorizontal(line, startPoint.x); + int endOffset = mLayout.getOffsetForHorizontal(line, endPoint.x); + if (startOffset == endOffset) { + return handleGestureFailure(gesture); + } else if (startOffset > endOffset) { + int tmp = startOffset; + startOffset = endOffset; + endOffset = tmp; + } + // TODO(b/247557062): The boundary offsets might be off by one. We should check which side + // of the offset the point is on, and adjust if necessary. + // TODO(b/247557062): This doesn't handle bidirectional text correctly. + + Pattern whitespacePattern = getWhitespacePattern(); + Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); + int lastRemoveOffset = -1; + while (matcher.find()) { + lastRemoveOffset = startOffset + matcher.start(); + getEditableText().delete(lastRemoveOffset, startOffset + matcher.end()); + startOffset = lastRemoveOffset; + endOffset -= matcher.end() - matcher.start(); + if (startOffset == endOffset) { + break; + } + matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); + } + if (lastRemoveOffset == -1) { + return handleGestureFailure(gesture); + } + Selection.setSelection(getEditableText(), lastRemoveOffset); + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + + /** @hide */ + public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) { + PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); + + int line = mLayout.getLineForVertical((int) point.y); + if (point.y < mLayout.getLineTop(line) + || point.y > mLayout.getLineBottomWithoutSpacing(line)) { + return handleGestureFailure(gesture); + } + if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) { + return handleGestureFailure(gesture); + } + + int startOffset = mLayout.getOffsetForHorizontal(line, point.x); + if (mLayout.isLevelBoundary(startOffset)) { + // TODO(b/247551937): Support gesture at level boundaries. + return handleGestureFailure(gesture); + } + + int endOffset = startOffset; + while (startOffset > 0 && Character.isWhitespace(mText.charAt(startOffset - 1))) { + startOffset--; + } + while (endOffset < mText.length() && Character.isWhitespace(mText.charAt(endOffset))) { + endOffset++; + } + if (startOffset < endOffset) { + getEditableText().delete(startOffset, endOffset); + Selection.setSelection(getEditableText(), startOffset); + } else { + // No whitespace found, so insert a space. + getEditableText().insert(startOffset, " "); + Selection.setSelection(getEditableText(), startOffset + 1); + } + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + private int handleGestureFailure(HandwritingGesture gesture) { if (!TextUtils.isEmpty(gesture.getFallbackText())) { getEditableText() @@ -9360,13 +9465,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Nullable private int[] getRangeForRect(@NonNull RectF area, int granularity) { - // The coordinates provided are screen coordinates - transform to content coordinates. - int[] screenToViewport = getLocationOnScreen(); - area = new RectF(area); - area.offset( - -(screenToViewport[0] + viewportToContentHorizontalOffset()), - -(screenToViewport[1] + viewportToContentVerticalOffset())); - SegmentIterator segmentIterator; if (granularity == HandwritingGesture.GRANULARITY_WORD) { WordIterator wordIterator = getWordIterator(); @@ -9379,6 +9477,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mLayout.getRangeForRect(area, segmentIterator); } + private Pattern getWhitespacePattern() { + if (mWhitespacePattern == null) { + mWhitespacePattern = Pattern.compile("\\s+"); + } + return mWhitespacePattern; + } + /** @hide */ @VisibleForTesting @UnsupportedAppUsage @@ -10631,6 +10736,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener r.bottom += verticalOffset; } + private PointF convertFromScreenToContentCoordinates(PointF point) { + int[] screenToViewport = getLocationOnScreen(); + PointF copy = new PointF(point); + copy.offset( + -(screenToViewport[0] + viewportToContentHorizontalOffset()), + -(screenToViewport[1] + viewportToContentVerticalOffset())); + return copy; + } + + private RectF convertFromScreenToContentCoordinates(RectF rect) { + int[] screenToViewport = getLocationOnScreen(); + RectF copy = new RectF(rect); + copy.offset( + -(screenToViewport[0] + viewportToContentHorizontalOffset()), + -(screenToViewport[1] + viewportToContentVerticalOffset())); + return copy; + } + int viewportToContentHorizontalOffset() { return getCompoundPaddingLeft() - mScrollX; } diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java index b8f39a990cdd..887d027d26a8 100644 --- a/core/java/android/window/ScreenCapture.java +++ b/core/java/android/window/ScreenCapture.java @@ -24,16 +24,11 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; import android.util.Log; import android.view.SurfaceControl; -import libcore.util.NativeAllocationRegistry; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; /** * Handles display and layer captures for the system. @@ -44,23 +39,18 @@ public class ScreenCapture { private static final String TAG = "ScreenCapture"; private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, - long captureListener); + ScreenCaptureListener captureListener); private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, - long captureListener); - private static native long nativeCreateScreenCaptureListener( - Consumer<ScreenshotHardwareBuffer> consumer); - private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out); - private static native long nativeReadListenerFromParcel(Parcel in); - private static native long getNativeListenerFinalizer(); + ScreenCaptureListener captureListener); /** - * @param captureArgs Arguments about how to take the screenshot + * @param captureArgs Arguments about how to take the screenshot * @param captureListener A listener to receive the screenshot callback * @hide */ public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs, @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject); + return nativeCaptureDisplay(captureArgs, captureListener); } /** @@ -71,8 +61,10 @@ public class ScreenCapture { */ public static ScreenshotHardwareBuffer captureDisplay( DisplayCaptureArgs captureArgs) { - SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); - int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener()); + SyncScreenCaptureListener + screenCaptureListener = new SyncScreenCaptureListener(); + + int status = captureDisplay(captureArgs, screenCaptureListener); if (status != 0) { return null; } @@ -83,13 +75,14 @@ public class ScreenCapture { /** * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled - * up/down. + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * * @return Returns a HardwareBuffer that contains the layer capture. * @hide */ @@ -101,14 +94,15 @@ public class ScreenCapture { /** * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled - * up/down. - * @param format The desired pixel format of the returned buffer. + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * @param format The desired pixel format of the returned buffer. + * * @return Returns a HardwareBuffer that contains the layer capture. * @hide */ @@ -130,7 +124,7 @@ public class ScreenCapture { LayerCaptureArgs captureArgs) { SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); - int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener()); + int status = captureLayers(captureArgs, screenCaptureListener); if (status != 0) { return null; } @@ -141,7 +135,6 @@ public class ScreenCapture { /** * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer * handles to exclude. - * * @hide */ public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer, @@ -157,13 +150,24 @@ public class ScreenCapture { } /** - * @param captureArgs Arguments about how to take the screenshot + * @param captureArgs Arguments about how to take the screenshot * @param captureListener A listener to receive the screenshot callback * @hide */ public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureLayers(captureArgs, captureListener.mNativeObject); + return nativeCaptureLayers(captureArgs, captureListener); + } + + /** + * @hide + */ + public interface ScreenCaptureListener { + /** + * The callback invoked when the screen capture is complete. + * @param hardwareBuffer Data containing info about the screen capture. + */ + void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer); } /** @@ -186,16 +190,15 @@ public class ScreenCapture { mContainsHdrLayers = containsHdrLayers; } - /** - * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. - * - * @param hardwareBuffer The existing HardwareBuffer object - * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} - * @param containsSecureLayers Indicates whether this graphic buffer contains captured - * contents of secure layers, in which case the screenshot - * should not be persisted. - * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. - */ + /** + * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. + * @param hardwareBuffer The existing HardwareBuffer object + * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} + * @param containsSecureLayers Indicates whether this graphic buffer contains captured + * contents of secure layers, in which case the screenshot + * should not be persisted. + * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. + */ private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); @@ -217,7 +220,6 @@ public class ScreenCapture { public boolean containsSecureLayers() { return mContainsSecureLayers; } - /** * Returns whether the screenshot contains at least one HDR layer. * This information may be useful for informing the display whether this screenshot @@ -232,7 +234,7 @@ public class ScreenCapture { * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap * into * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)} - * <p> + * * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to * directly * use the {@link HardwareBuffer} directly. @@ -248,23 +250,44 @@ public class ScreenCapture { } } + private static class SyncScreenCaptureListener implements ScreenCaptureListener { + private static final int SCREENSHOT_WAIT_TIME_S = 1; + private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; + private final CountDownLatch mCountDownLatch = new CountDownLatch(1); + + @Override + public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { + mScreenshotHardwareBuffer = hardwareBuffer; + mCountDownLatch.countDown(); + } + + private ScreenshotHardwareBuffer waitForScreenshot() { + try { + mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for screen capture result", e); + } + + return mScreenshotHardwareBuffer; + } + } + /** * A common arguments class used for various screenshot requests. This contains arguments that * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs} - * * @hide */ - public static class CaptureArgs implements Parcelable { - public final int mPixelFormat; - public final Rect mSourceCrop = new Rect(); - public final float mFrameScaleX; - public final float mFrameScaleY; - public final boolean mCaptureSecureLayers; - public final boolean mAllowProtected; - public final long mUid; - public final boolean mGrayscale; - - private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) { + private abstract static class CaptureArgs { + private final int mPixelFormat; + private final Rect mSourceCrop = new Rect(); + private final float mFrameScaleX; + private final float mFrameScaleY; + private final boolean mCaptureSecureLayers; + private final boolean mAllowProtected; + private final long mUid; + private final boolean mGrayscale; + + private CaptureArgs(Builder<? extends Builder<?>> builder) { mPixelFormat = builder.mPixelFormat; mSourceCrop.set(builder.mSourceCrop); mFrameScaleX = builder.mFrameScaleX; @@ -275,23 +298,12 @@ public class ScreenCapture { mGrayscale = builder.mGrayscale; } - private CaptureArgs(Parcel in) { - mPixelFormat = in.readInt(); - mSourceCrop.readFromParcel(in); - mFrameScaleX = in.readFloat(); - mFrameScaleY = in.readFloat(); - mCaptureSecureLayers = in.readBoolean(); - mAllowProtected = in.readBoolean(); - mUid = in.readLong(); - mGrayscale = in.readBoolean(); - } - /** * The Builder class used to construct {@link CaptureArgs} * - * @param <T> A builder that extends {@link CaptureArgs.Builder} + * @param <T> A builder that extends {@link Builder} */ - public static class Builder<T extends CaptureArgs.Builder<T>> { + abstract static class Builder<T extends Builder<T>> { private int mPixelFormat = PixelFormat.RGBA_8888; private final Rect mSourceCrop = new Rect(); private float mFrameScaleX = 1; @@ -302,14 +314,6 @@ public class ScreenCapture { private boolean mGrayscale; /** - * Construct a new {@link CaptureArgs} with the set parameters. The builder remains - * valid. - */ - public CaptureArgs build() { - return new CaptureArgs(this); - } - - /** * The desired pixel format of the returned buffer. */ public T setPixelFormat(int pixelFormat) { @@ -391,47 +395,15 @@ public class ScreenCapture { /** * Each sub class should return itself to allow the builder to chain properly */ - T getThis() { - return (T) this; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mPixelFormat); - mSourceCrop.writeToParcel(dest, flags); - dest.writeFloat(mFrameScaleX); - dest.writeFloat(mFrameScaleY); - dest.writeBoolean(mCaptureSecureLayers); - dest.writeBoolean(mAllowProtected); - dest.writeLong(mUid); - dest.writeBoolean(mGrayscale); + abstract T getThis(); } - - public static final Parcelable.Creator<CaptureArgs> CREATOR = - new Parcelable.Creator<CaptureArgs>() { - @Override - public CaptureArgs createFromParcel(Parcel in) { - return new CaptureArgs(in); - } - - @Override - public CaptureArgs[] newArray(int size) { - return new CaptureArgs[size]; - } - }; } /** * The arguments class used to make display capture requests. * + * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener) * @hide - * @see #nativeCaptureDisplay(DisplayCaptureArgs, long) */ public static class DisplayCaptureArgs extends CaptureArgs { private final IBinder mDisplayToken; @@ -516,8 +488,8 @@ public class ScreenCapture { /** * The arguments class used to make layer capture requests. * + * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener) * @hide - * @see #nativeCaptureLayers(LayerCaptureArgs, long) */ public static class LayerCaptureArgs extends CaptureArgs { private final long mNativeLayer; @@ -558,17 +530,6 @@ public class ScreenCapture { return new LayerCaptureArgs(this); } - public Builder(SurfaceControl layer, CaptureArgs args) { - setLayer(layer); - setPixelFormat(args.mPixelFormat); - setSourceCrop(args.mSourceCrop); - setFrameScale(args.mFrameScaleX, args.mFrameScaleY); - setCaptureSecureLayers(args.mCaptureSecureLayers); - setAllowProtected(args.mAllowProtected); - setUid(args.mUid); - setGrayscale(args.mGrayscale); - } - public Builder(SurfaceControl layer) { setLayer(layer); } @@ -581,6 +542,7 @@ public class ScreenCapture { return this; } + /** * An array of layer handles to exclude. */ @@ -602,106 +564,8 @@ public class ScreenCapture { Builder getThis() { return this; } - } - } - /** - * The object used to receive the results when invoking screen capture requests via - * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or - * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} - */ - public static class ScreenCaptureListener implements Parcelable { - private final long mNativeObject; - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer()); - - /** - * @param consumer The callback invoked when the screen capture is complete. - */ - public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) { - mNativeObject = nativeCreateScreenCaptureListener(consumer); - sRegistry.registerNativeAllocation(this, mNativeObject); } - - private ScreenCaptureListener(Parcel in) { - if (in.readBoolean()) { - mNativeObject = nativeReadListenerFromParcel(in); - sRegistry.registerNativeAllocation(this, mNativeObject); - } else { - mNativeObject = 0; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - if (mNativeObject == 0) { - dest.writeBoolean(false); - } else { - dest.writeBoolean(true); - nativeWriteListenerToParcel(mNativeObject, dest); - } - } - - public static final Parcelable.Creator<ScreenCaptureListener> CREATOR = - new Parcelable.Creator<ScreenCaptureListener>() { - @Override - public ScreenCaptureListener createFromParcel(Parcel in) { - return new ScreenCaptureListener(in); - } - - @Override - public ScreenCaptureListener[] newArray(int size) { - return new ScreenCaptureListener[0]; - } - }; } - /** - * A helper class to handle the async screencapture callbacks synchronously. This should only - * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot. - */ - public static class SyncScreenCaptureListener { - private static final int SCREENSHOT_WAIT_TIME_S = 1; - private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; - private final CountDownLatch mCountDownLatch = new CountDownLatch(1); - private final ScreenCaptureListener mScreenCaptureListener; - - public SyncScreenCaptureListener() { - mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> { - mScreenshotHardwareBuffer = screenshotHardwareBuffer; - mCountDownLatch.countDown(); - }); - } - - /** - * @return The underlying {@link ScreenCaptureListener} - */ - public ScreenCaptureListener getScreenCaptureListener() { - return mScreenCaptureListener; - } - - /** - * Waits until the screenshot callback has been invoked and the screenshot is ready. This - * can return {@code null} if the screenshot callback wasn't invoked after - * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error - * - * @return A ScreenshotHardwareBuffer for the content that was captured. - */ - @Nullable - public ScreenshotHardwareBuffer waitForScreenshot() { - try { - mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); - } catch (Exception e) { - Log.e(TAG, "Failed to wait for screen capture result", e); - } - - return mScreenshotHardwareBuffer; - } - } } diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 56e910769cb5..e2c8a31cc987 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -188,6 +188,10 @@ public final class TaskFragmentInfo implements Parcelable { /** * Returns {@code true} if the parameters that are important for task fragment organizers are * equal between this {@link TaskFragmentInfo} and {@param that}. + * Note that this method is usually called with + * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( + * Configuration, Configuration)} to determine if this {@link TaskFragmentInfo} should + * be dispatched to the client. */ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) { if (that == null) { diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/TaskFragmentParentInfo.aidl index 267a7c60b60d..79d2209ab244 100644 --- a/core/java/android/window/ScreenCapture.aidl +++ b/core/java/android/window/TaskFragmentParentInfo.aidl @@ -16,11 +16,8 @@ package android.window; -/** @hide */ -parcelable ScreenCapture.CaptureArgs; - -/** @hide */ -parcelable ScreenCapture.ScreenshotHardwareBuffer; - -/** @hide */ -parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file +/** + * The information about the parent Task of a particular TaskFragment + * @hide + */ +parcelable TaskFragmentParentInfo;
\ No newline at end of file diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java new file mode 100644 index 000000000000..64b2638407df --- /dev/null +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.res.Configuration; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The information about the parent Task of a particular TaskFragment + * @hide + */ +public class TaskFragmentParentInfo implements Parcelable { + @NonNull + private final Configuration mConfiguration = new Configuration(); + + private final int mDisplayId; + + private final boolean mVisibleRequested; + + public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, + boolean visibleRequested) { + mConfiguration.setTo(configuration); + mDisplayId = displayId; + mVisibleRequested = visibleRequested; + } + + public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mConfiguration.setTo(info.getConfiguration()); + mDisplayId = info.mDisplayId; + mVisibleRequested = info.mVisibleRequested; + } + + /** The {@link Configuration} of the parent Task */ + @NonNull + public Configuration getConfiguration() { + return mConfiguration; + } + + /** + * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the + * Task is detached from previously associated display. + */ + public int getDisplayId() { + return mDisplayId; + } + + /** Whether the parent Task is requested to be visible or not */ + public boolean isVisibleRequested() { + return mVisibleRequested; + } + + /** + * Returns {@code true} if the parameters which are important for task fragment + * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}. + * Note that this method is usually called with + * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( + * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should + * be dispatched to the client. + */ + public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) { + if (that == null) { + return false; + } + return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId + && mVisibleRequested == that.mVisibleRequested; + } + + @WindowConfiguration.WindowingMode + private int getWindowingMode() { + return mConfiguration.windowConfiguration.getWindowingMode(); + } + + @Override + public String toString() { + return TaskFragmentParentInfo.class.getSimpleName() + ":{" + + "config=" + mConfiguration + + ", displayId=" + mDisplayId + + ", visibleRequested=" + mVisibleRequested + + "}"; + } + + /** + * Indicates that whether this {@link TaskFragmentParentInfo} equals to {@code obj}. + * Note that {@link #equalsForTaskFragmentOrganizer(TaskFragmentParentInfo)} should be used + * for most cases because not all {@link Configuration} properties are interested for + * {@link TaskFragmentOrganizer}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TaskFragmentParentInfo)) { + return false; + } + final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj; + return mConfiguration.equals(that.mConfiguration) + && mDisplayId == that.mDisplayId + && mVisibleRequested == that.mVisibleRequested; + } + + @Override + public int hashCode() { + int result = mConfiguration.hashCode(); + result = 31 * result + mDisplayId; + result = 31 * result + (mVisibleRequested ? 1 : 0); + return result; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mConfiguration.writeToParcel(dest, flags); + dest.writeInt(mDisplayId); + dest.writeBoolean(mVisibleRequested); + } + + private TaskFragmentParentInfo(Parcel in) { + mConfiguration.readFromParcel(in); + mDisplayId = in.readInt(); + mVisibleRequested = in.readBoolean(); + } + + public static final Creator<TaskFragmentParentInfo> CREATOR = + new Creator<TaskFragmentParentInfo>() { + @Override + public TaskFragmentParentInfo createFromParcel(Parcel in) { + return new TaskFragmentParentInfo(in); + } + + @Override + public TaskFragmentParentInfo[] newArray(int size) { + return new TaskFragmentParentInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 04fcd3afcf1d..76677431d6fa 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -173,10 +173,6 @@ public final class TaskFragmentTransaction implements Parcelable { /** @see #setTaskId(int) */ private int mTaskId; - /** @see #setTaskConfiguration(Configuration) */ - @Nullable - private Configuration mTaskConfiguration; - /** @see #setErrorCallbackToken(IBinder) */ @Nullable private IBinder mErrorCallbackToken; @@ -193,6 +189,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private IBinder mActivityToken; + @Nullable + private TaskFragmentParentInfo mTaskFragmentParentInfo; + public Change(@ChangeType int type) { mType = type; } @@ -202,11 +201,11 @@ public final class TaskFragmentTransaction implements Parcelable { mTaskFragmentToken = in.readStrongBinder(); mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); mTaskId = in.readInt(); - mTaskConfiguration = in.readTypedObject(Configuration.CREATOR); mErrorCallbackToken = in.readStrongBinder(); mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader()); mActivityIntent = in.readTypedObject(Intent.CREATOR); mActivityToken = in.readStrongBinder(); + mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); } @Override @@ -215,11 +214,11 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeStrongBinder(mTaskFragmentToken); dest.writeTypedObject(mTaskFragmentInfo, flags); dest.writeInt(mTaskId); - dest.writeTypedObject(mTaskConfiguration, flags); dest.writeStrongBinder(mErrorCallbackToken); dest.writeBundle(mErrorBundle); dest.writeTypedObject(mActivityIntent, flags); dest.writeStrongBinder(mActivityToken); + dest.writeTypedObject(mTaskFragmentParentInfo, flags); } /** The change is related to the TaskFragment created with this unique token. */ @@ -243,10 +242,10 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } + // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. /** Configuration of the parent Task. */ @NonNull public Change setTaskConfiguration(@NonNull Configuration configuration) { - mTaskConfiguration = requireNonNull(configuration); return this; } @@ -294,6 +293,19 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } + // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. + /** + * Sets info of the parent Task of the embedded TaskFragment. + * @see TaskFragmentParentInfo + * + * @hide pending unhide + */ + @NonNull + public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mTaskFragmentParentInfo = requireNonNull(info); + return this; + } + @ChangeType public int getType() { return mType; @@ -313,9 +325,10 @@ public final class TaskFragmentTransaction implements Parcelable { return mTaskId; } + // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. @Nullable public Configuration getTaskConfiguration() { - return mTaskConfiguration; + return mTaskFragmentParentInfo.getConfiguration(); } @Nullable @@ -339,6 +352,13 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } + // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. + /** @hide pending unhide */ + @Nullable + public TaskFragmentParentInfo getTaskFragmentParentInfo() { + return mTaskFragmentParentInfo; + } + @Override public String toString() { return "Change{ type=" + mType + " }"; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 33ea2e4af473..641d1a189711 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -125,8 +125,15 @@ public final class TransitionInfo implements Parcelable { /** The container attaches work profile thumbnail for cross profile animation. */ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** + * Whether the window is covered by an app starting window. This is different from + * {@link #FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT} which is only set on the Activity window + * that contains the starting window. + */ + public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 14; + public static final int FLAG_FIRST_CUSTOM = 1 << 15; /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { @@ -145,6 +152,7 @@ public final class TransitionInfo implements Parcelable { FLAG_WILL_IME_SHOWN, FLAG_CROSS_PROFILE_OWNER_THUMBNAIL, FLAG_CROSS_PROFILE_WORK_THUMBNAIL, + FLAG_IS_BEHIND_STARTING_WINDOW, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -351,6 +359,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_FILLS_TASK) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK"); } + if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index a99c6bef202f..2d29c5946ede 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -694,6 +694,23 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Finishes the Activity. + * Comparing to directly calling {@link android.app.Activity#finish()}, calling this can make + * sure the finishing happens in the same transaction with other operations. + * @param activityToken activity to be finished. + */ + @NonNull + public WindowContainerTransaction finishActivity(@NonNull IBinder activityToken) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY) + .setContainer(activityToken) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** * Sets/removes the always on top flag for this {@code windowContainer}. See * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. * Please note that this method is only intended to be used for a @@ -1151,6 +1168,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18; public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19; public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20; + public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1472,6 +1490,8 @@ public final class WindowContainerTransaction implements Parcelable { + " alwaysOnTop=" + mAlwaysOnTop + "}"; case HIERARCHY_OP_TYPE_REMOVE_TASK: return "{RemoveTask: task=" + mContainer + "}"; + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: + return "{finishActivity: activity=" + mContainer + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 5cf7e364bbc1..c8eec7dbd486 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1069,7 +1069,12 @@ public class ChooserActivity extends ResolverActivity implements } } - private ViewGroup createContentPreviewView(ViewGroup parent) { + /** + * Create a view that will be shown in the content preview area + * @param parent reference to the parent container where the view should be attached to + * @return content preview view + */ + protected ViewGroup createContentPreviewView(ViewGroup parent) { Intent targetIntent = getTargetIntent(); int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent); @@ -2653,7 +2658,7 @@ public class ChooserActivity extends ResolverActivity implements boolean isExpandable = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode(); - if (directShareHeight != 0 && isSendAction(getTargetIntent()) + if (directShareHeight != 0 && shouldShowContentPreview() && isExpandable) { // make sure to leave room for direct share 4->8 expansion int requiredExpansionHeight = @@ -2901,7 +2906,14 @@ public class ChooserActivity extends ResolverActivity implements return shouldShowTabs() && mMultiProfilePagerAdapter.getListAdapterForUserHandle( UserHandle.of(UserHandle.myUserId())).getCount() > 0 - && isSendAction(getTargetIntent()); + && shouldShowContentPreview(); + } + + /** + * @return true if we want to show the content preview area + */ + protected boolean shouldShowContentPreview() { + return isSendAction(getTargetIntent()); } private void updateStickyContentPreview() { @@ -3234,7 +3246,7 @@ public class ChooserActivity extends ResolverActivity implements return 0; } - if (!isSendAction(getTargetIntent())) { + if (!shouldShowContentPreview()) { return 0; } @@ -3265,7 +3277,7 @@ public class ChooserActivity extends ResolverActivity implements // There can be at most one row in the listview, that is internally // a ViewGroup with 2 rows public int getServiceTargetRowCount() { - if (isSendAction(getTargetIntent()) + if (shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic()) { return 1; } diff --git a/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl new file mode 100644 index 000000000000..b2236c9f4907 --- /dev/null +++ b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +/** + * IPC interface for an application to receive callbacks from the log access dialog callback. + */ +oneway interface ILogAccessDialogCallback { + void approveAccessForClient(int uid, String packageName); + void declineAccessForClient(int uid, String packageName); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/core/java/com/android/internal/app/LogAccessDialogActivity.java index 811e96ce6d82..4adb8673084b 100644 --- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java +++ b/core/java/com/android/internal/app/LogAccessDialogActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.logcat; +package com.android.internal.app; import android.annotation.StyleRes; import android.app.Activity; @@ -27,7 +27,14 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; +import android.text.Html; +import android.text.Spannable; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.TypefaceSpan; +import android.text.style.URLSpan; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.InflateException; @@ -37,7 +44,6 @@ import android.widget.Button; import android.widget.TextView; import com.android.internal.R; -import com.android.server.LocalServices; /** * Dialog responsible for obtaining user consent per-use log access @@ -45,17 +51,19 @@ import com.android.server.LocalServices; public class LogAccessDialogActivity extends Activity implements View.OnClickListener { private static final String TAG = LogAccessDialogActivity.class.getSimpleName(); + public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK"; + private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000; private static final int MSG_DISMISS_DIALOG = 0; - private final LogcatManagerService.LogcatManagerServiceInternal mLogcatManagerInternal = - LocalServices.getService(LogcatManagerService.LogcatManagerServiceInternal.class); - private String mPackageName; private int mUid; + private ILogAccessDialogCallback mCallback; private String mAlertTitle; + private String mAlertBody; + private String mAlertLearnMore; private AlertDialog.Builder mAlertDialog; private AlertDialog mAlert; private View mAlertView; @@ -81,6 +89,9 @@ public class LogAccessDialogActivity extends Activity implements return; } + mAlertBody = getResources().getString(R.string.log_access_confirmation_body); + mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more); + // create View boolean isDarkTheme = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; @@ -118,6 +129,13 @@ public class LogAccessDialogActivity extends Activity implements return false; } + mCallback = ILogAccessDialogCallback.Stub.asInterface( + intent.getExtras().getBinder(EXTRA_CALLBACK)); + if (mCallback == null) { + Slog.e(TAG, "Missing callback"); + return false; + } + mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); if (mPackageName == null || mPackageName.length() == 0) { Slog.e(TAG, "Missing package name extra"); @@ -165,13 +183,22 @@ public class LogAccessDialogActivity extends Activity implements return titleString; } + private Spannable styleFont(String text) { + Spannable s = (Spannable) Html.fromHtml(text); + for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) { + TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans"); + s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0); + } + return s; + } + /** * Returns the dialog view. * If we cannot retrieve the package name, it returns null and we decline the full device log * access */ private View createView(@StyleRes int themeId) { - Context themedContext = new ContextThemeWrapper(getApplicationContext(), themeId); + Context themedContext = new ContextThemeWrapper(this, themeId); final View view = LayoutInflater.from(themedContext).inflate( R.layout.log_access_user_consent_dialog_permission, null /*root*/); @@ -182,6 +209,19 @@ public class LogAccessDialogActivity extends Activity implements ((TextView) view.findViewById(R.id.log_access_dialog_title)) .setText(mAlertTitle); + if (!TextUtils.isEmpty(mAlertLearnMore)) { + Spannable mSpannableLearnMore = styleFont(mAlertLearnMore); + + ((TextView) view.findViewById(R.id.log_access_dialog_body)) + .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore)); + + ((TextView) view.findViewById(R.id.log_access_dialog_body)) + .setMovementMethod(LinkMovementMethod.getInstance()); + } else { + ((TextView) view.findViewById(R.id.log_access_dialog_body)) + .setText(mAlertBody); + } + Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button); button_allow.setOnClickListener(this); @@ -194,19 +234,27 @@ public class LogAccessDialogActivity extends Activity implements @Override public void onClick(View view) { - switch (view.getId()) { - case R.id.log_access_dialog_allow_button: - mLogcatManagerInternal.approveAccessForClient(mUid, mPackageName); - finish(); - break; - case R.id.log_access_dialog_deny_button: - declineLogAccess(); - finish(); - break; + try { + switch (view.getId()) { + case R.id.log_access_dialog_allow_button: + mCallback.approveAccessForClient(mUid, mPackageName); + finish(); + break; + case R.id.log_access_dialog_deny_button: + declineLogAccess(); + finish(); + break; + } + } catch (RemoteException e) { + finish(); } } private void declineLogAccess() { - mLogcatManagerInternal.declineAccessForClient(mUid, mPackageName); + try { + mCallback.declineAccessForClient(mUid, mPackageName); + } catch (RemoteException e) { + finish(); + } } } diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java index 354eb62ba045..be0b729aedfe 100644 --- a/core/java/com/android/internal/app/SimpleIconFactory.java +++ b/core/java/com/android/internal/app/SimpleIconFactory.java @@ -51,6 +51,7 @@ import android.util.Pools.SynchronizedPool; import android.util.TypedValue; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; @@ -69,6 +70,7 @@ public class SimpleIconFactory { private static final SynchronizedPool<SimpleIconFactory> sPool = new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); + private static boolean sPoolEnabled = true; private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; private static final float BLUR_FACTOR = 1.5f / 48; @@ -92,7 +94,7 @@ public class SimpleIconFactory { */ @Deprecated public static SimpleIconFactory obtain(Context ctx) { - SimpleIconFactory instance = sPool.acquire(); + SimpleIconFactory instance = sPoolEnabled ? sPool.acquire() : null; if (instance == null) { final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE); final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity(); @@ -106,6 +108,17 @@ public class SimpleIconFactory { return instance; } + /** + * Enables or disables SimpleIconFactory objects pooling. It is enabled in production, you + * could use this method in tests and disable the pooling to make the icon rendering more + * deterministic because some sizing parameters will not be cached. Please ensure that you + * reset this value back after finishing the test. + */ + @VisibleForTesting + public static void setPoolEnabled(boolean poolEnabled) { + sPoolEnabled = poolEnabled; + } + private static int getAttrDimFromContext(Context ctx, @AttrRes int attrId, String errorMsg) { final Resources res = ctx.getResources(); TypedValue outVal = new TypedValue(); diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 87e8ac1be611..72b9cd272d02 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -473,7 +473,10 @@ public final class ProcessState { } } mCurCombinedState = state; - mStats.mUidStates.get(mUid).updateCombinedState(state, now); + final UidState uidState = mStats.mUidStates.get(mUid); + if (uidState != null) { + uidState.updateCombinedState(state, now); + } } } diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 1e5208739314..f260d7dfc6a6 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -41,6 +41,8 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.widget.TextView; @@ -277,6 +279,10 @@ public final class EditableInputConnection extends BaseInputConnection result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture); } else if (gesture instanceof InsertGesture) { result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture); + } else if (gesture instanceof RemoveSpaceGesture) { + result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture); + } else if (gesture instanceof JoinOrSplitGesture) { + result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture); } else { result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED; } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index b5991b3fcbdc..40d192ea6827 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -29,7 +29,9 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; @@ -220,6 +222,8 @@ public class InteractionJankMonitor { public static final int CUJ_TASKBAR_EXPAND = 60; public static final int CUJ_TASKBAR_COLLAPSE = 61; public static final int CUJ_SHADE_CLEAR_ALL = 62; + public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; + public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; private static final int NO_STATSD_LOGGING = -1; @@ -291,6 +295,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION, }; private static class InstanceHolder { @@ -377,7 +383,9 @@ public class InteractionJankMonitor { CUJ_USER_DIALOG_OPEN, CUJ_TASKBAR_EXPAND, CUJ_TASKBAR_COLLAPSE, - CUJ_SHADE_CLEAR_ALL + CUJ_SHADE_CLEAR_ALL, + CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, + CUJ_LOCKSCREEN_OCCLUSION }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -434,6 +442,22 @@ public class InteractionJankMonitor { @VisibleForTesting public FrameTracker createFrameTracker(Configuration config, Session session) { final View view = config.mView; + + if (!config.hasValidView()) { + boolean attached = false; + boolean hasViewRoot = false; + boolean hasRenderer = false; + if (view != null) { + attached = view.isAttachedToWindow(); + hasViewRoot = view.getViewRootImpl() != null; + hasRenderer = view.getThreadedRenderer() != null; + } + Log.d(TAG, "create FrameTracker fails: view=" + view + + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot + + ", hasRenderer=" + hasRenderer, new Throwable()); + return null; + } + final ThreadedRendererWrapper threadedRenderer = view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); final ViewRootWrapper viewRoot = @@ -541,6 +565,7 @@ public class InteractionJankMonitor { // begin a new trace session. tracker = createFrameTracker(conf, new Session(cujType, conf.mTag)); + if (tracker == null) return false; putTracker(cujType, tracker); tracker.begin(); @@ -868,6 +893,10 @@ public class InteractionJankMonitor { return "TASKBAR_COLLAPSE"; case CUJ_SHADE_CLEAR_ALL: return "SHADE_CLEAR_ALL"; + case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION: + return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; + case CUJ_LOCKSCREEN_OCCLUSION: + return "LOCKSCREEN_OCCLUSION"; } return "UNKNOWN"; } @@ -1058,9 +1087,19 @@ public class InteractionJankMonitor { msg.append("Must pass in a valid surface control if only instrument surface; "); } } else { - if (mView == null || !mView.isAttachedToWindow()) { + if (!hasValidView()) { shouldThrow = true; - msg.append("Null view or unattached view while instrumenting view; "); + boolean attached = false; + boolean hasViewRoot = false; + boolean hasRenderer = false; + if (mView != null) { + attached = mView.isAttachedToWindow(); + hasViewRoot = mView.getViewRootImpl() != null; + hasRenderer = mView.getThreadedRenderer() != null; + } + String err = "invalid view: view=" + mView + ", attached=" + attached + + ", hasViewRoot=" + hasViewRoot + ", hasRenderer=" + hasRenderer; + msg.append(err); } } if (shouldThrow) { @@ -1068,6 +1107,12 @@ public class InteractionJankMonitor { } } + boolean hasValidView() { + return mSurfaceOnly + || (mView != null && mView.isAttachedToWindow() + && mView.getViewRootImpl() != null && mView.getThreadedRenderer() != null); + } + /** * @return true if only instrumenting surface, false otherwise */ diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index b1e7d15cbf4a..deafd19e866f 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1001,16 +1001,24 @@ public final class Zygote { } /** + * This will enable jdwp by default for all apps. It is OK to cache this property + * because we expect to reboot the system whenever this property changes + */ + private static final boolean ENABLE_JDWP = SystemProperties.get( + "persist.debug.dalvik.vm.jdwp.enabled").equals("1"); + + /** * Applies debugger system properties to the zygote arguments. * - * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, - * the debugger state is specified via the "--enable-jdwp" flag - * in the spawn request. + * For eng builds all apps are debuggable. On userdebug and user builds + * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are + * debuggable. Otherwise, the debugger state is specified via the + * "--enable-jdwp" flag in the spawn request. * * @param args non-null; zygote spawner args */ static void applyDebuggerSystemProperty(ZygoteArguments args) { - if (RoSystemProperties.DEBUGGABLE) { + if (Build.IS_ENG || ENABLE_JDWP) { args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; } } diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 0e8dc071cbdc..8f943ef654f9 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -88,6 +88,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, "CoreBackPreview"), + WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index d3f9e0a2729e..8fcb6d5514d4 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -29,6 +29,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS; @@ -174,6 +175,12 @@ public class LatencyTracker { */ public static final int ACTION_FOLD_TO_AOD = 18; + /** + * Time it takes to show the {@link android.service.voice.VoiceInteractionSession} system UI + * after a {@link android.hardware.soundtrigger3.ISoundTriggerHw} voice trigger. + */ + public static final int ACTION_SHOW_VOICE_INTERACTION = 19; + private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, @@ -194,6 +201,7 @@ public class LatencyTracker { ACTION_LOAD_SHARE_SHEET, ACTION_SHOW_SELECTION_TOOLBAR, ACTION_FOLD_TO_AOD, + ACTION_SHOW_VOICE_INTERACTION, }; /** @hide */ @@ -217,6 +225,7 @@ public class LatencyTracker { ACTION_LOAD_SHARE_SHEET, ACTION_SHOW_SELECTION_TOOLBAR, ACTION_FOLD_TO_AOD, + ACTION_SHOW_VOICE_INTERACTION, }) @Retention(RetentionPolicy.SOURCE) public @interface Action { @@ -243,6 +252,7 @@ public class LatencyTracker { UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET, UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR, UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION, }; private static LatencyTracker sLatencyTracker; @@ -340,6 +350,8 @@ public class LatencyTracker { return "ACTION_SHOW_SELECTION_TOOLBAR"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD: return "ACTION_FOLD_TO_AOD"; + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION: + return "ACTION_SHOW_VOICE_INTERACTION"; default: throw new IllegalArgumentException("Invalid action"); } diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 9474f6fc3252..79c519645a24 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -377,6 +377,9 @@ public class ScreenshotHelper { msg.replyTo = new Messenger(h); if (mScreenshotConnection == null || mScreenshotService == null) { + if (mScreenshotConnection != null) { + resetConnection(); + } final ComponentName serviceComponent = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_screenshotServiceComponent)); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 3e0a6cb1cae0..65c2d00fda06 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -164,6 +164,7 @@ public class LockPatternUtils { private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info"; private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents"; + private static final String KNOWN_TRUST_AGENTS = "lockscreen.knowntrustagents"; private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged"; public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle"; @@ -1089,31 +1090,50 @@ public class LockPatternUtils { return getString(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, userId) != null; } + /** Updates the list of enabled trust agent in LockSettings storage for the given user. */ public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) { + setString(ENABLED_TRUST_AGENTS, serializeTrustAgents(activeTrustAgents), userId); + getTrustManager().reportEnabledTrustAgentsChanged(userId); + } + + /** Returns the list of enabled trust agent in LockSettings storage for the given user. */ + public List<ComponentName> getEnabledTrustAgents(int userId) { + return deserializeTrustAgents(getString(ENABLED_TRUST_AGENTS, userId)); + } + + /** Updates the list of known trust agent in LockSettings storage for the given user. */ + public void setKnownTrustAgents(Collection<ComponentName> knownTrustAgents, int userId) { + setString(KNOWN_TRUST_AGENTS, serializeTrustAgents(knownTrustAgents), userId); + } + + /** Returns the list of known trust agent in LockSettings storage for the given user. */ + public List<ComponentName> getKnownTrustAgents(int userId) { + return deserializeTrustAgents(getString(KNOWN_TRUST_AGENTS, userId)); + } + + private String serializeTrustAgents(Collection<ComponentName> trustAgents) { StringBuilder sb = new StringBuilder(); - for (ComponentName cn : activeTrustAgents) { + for (ComponentName cn : trustAgents) { if (sb.length() > 0) { sb.append(','); } sb.append(cn.flattenToShortString()); } - setString(ENABLED_TRUST_AGENTS, sb.toString(), userId); - getTrustManager().reportEnabledTrustAgentsChanged(userId); + return sb.toString(); } - public List<ComponentName> getEnabledTrustAgents(int userId) { - String serialized = getString(ENABLED_TRUST_AGENTS, userId); - if (TextUtils.isEmpty(serialized)) { - return new ArrayList<ComponentName>(); + private List<ComponentName> deserializeTrustAgents(String serializedTrustAgents) { + if (TextUtils.isEmpty(serializedTrustAgents)) { + return new ArrayList<>(); } - String[] split = serialized.split(","); - ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length); + String[] split = serializedTrustAgents.split(","); + ArrayList<ComponentName> trustAgents = new ArrayList<>(split.length); for (String s : split) { if (!TextUtils.isEmpty(s)) { - activeTrustAgents.add(ComponentName.unflattenFromString(s)); + trustAgents.add(ComponentName.unflattenFromString(s)); } } - return activeTrustAgents; + return trustAgents; } /** @@ -1487,7 +1507,8 @@ public class LockPatternUtils { STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, - STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT}) + STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, + SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1540,6 +1561,12 @@ public class LockPatternUtils { public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80; /** + * Some authentication is required because the trustagent either timed out or was disabled + * manually. + */ + public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100; + + /** * Strong auth flags that do not prevent biometric methods from being accepted as auth. * If any other flags are set, biometric authentication is disabled. */ diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp index f7a98d16f2e3..30d9ea19be39 100644 --- a/core/jni/android_os_MessageQueue.cpp +++ b/core/jni/android_os_MessageQueue.cpp @@ -51,6 +51,21 @@ public: virtual int handleEvent(int fd, int events, void* data); + /** + * A simple proxy that holds a weak reference to a looper callback. + */ + class WeakLooperCallback : public LooperCallback { + protected: + virtual ~WeakLooperCallback(); + + public: + WeakLooperCallback(const wp<LooperCallback>& callback); + virtual int handleEvent(int fd, int events, void* data); + + private: + wp<LooperCallback> mCallback; + }; + private: JNIEnv* mPollEnv; jobject mPollObj; @@ -131,7 +146,8 @@ void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) { if (events & CALLBACK_EVENT_OUTPUT) { looperEvents |= Looper::EVENT_OUTPUT; } - mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this, + mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, + sp<WeakLooperCallback>::make(this), reinterpret_cast<void*>(events)); } else { mLooper->removeFd(fd); @@ -162,6 +178,24 @@ int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) { } +// --- NativeMessageQueue::WeakLooperCallback --- + +NativeMessageQueue::WeakLooperCallback::WeakLooperCallback(const wp<LooperCallback>& callback) : + mCallback(callback) { +} + +NativeMessageQueue::WeakLooperCallback::~WeakLooperCallback() { +} + +int NativeMessageQueue::WeakLooperCallback::handleEvent(int fd, int events, void* data) { + sp<LooperCallback> callback = mCallback.promote(); + if (callback != nullptr) { + return callback->handleEvent(fd, events, data); + } + return 0; +} + + // ---------------------------------------------------------------------------- sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) { diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 8c23b214b8fe..b5a78b0fc46b 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -39,6 +39,7 @@ #include "androidfw/AssetManager2.h" #include "androidfw/AttributeResolution.h" #include "androidfw/MutexGuard.h" +#include <androidfw/ResourceTimer.h> #include "androidfw/ResourceTypes.h" #include "androidfw/ResourceUtils.h" @@ -630,6 +631,7 @@ static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin jshort density, jobject typed_value, jboolean resolve_references) { ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + ResourceTimer _tag(ResourceTimer::Counter::GetResourceValue); auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/, static_cast<uint16_t>(density)); if (!value.has_value()) { @@ -1232,6 +1234,7 @@ static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong pt } ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + ResourceTimer _tag(ResourceTimer::Counter::RetrieveAttributes); ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); auto result = RetrieveAttributes(assetmanager.get(), xml_parser, reinterpret_cast<uint32_t*>(attrs), @@ -1293,6 +1296,10 @@ static void NativeThemeRebase(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong th } else { CHECK(style_count == 0) << "style_ids is null while style_count is non-zero"; } + auto style_id_args_copy = std::vector<uint32_t>{style_id_args, style_id_args + style_count}; + if (style_ids != nullptr) { + env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT); + } jboolean* force_args = nullptr; if (force != nullptr) { @@ -1305,15 +1312,14 @@ static void NativeThemeRebase(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong th } else { CHECK(style_count == 0) << "force is null while style_count is non-zero"; } - - auto theme = reinterpret_cast<Theme*>(theme_ptr); - theme->Rebase(&(*assetmanager), style_id_args, force_args, static_cast<size_t>(style_count)); - if (style_ids != nullptr) { - env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT); - } + auto force_args_copy = std::vector<jboolean>{force_args, force_args + style_count}; if (force != nullptr) { env->ReleasePrimitiveArrayCritical(force, force_args, JNI_ABORT); } + + auto theme = reinterpret_cast<Theme*>(theme_ptr); + theme->Rebase(&(*assetmanager), style_id_args_copy.data(), force_args_copy.data(), + static_cast<size_t>(style_count)); } static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_asset_manager_ptr, diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp index 891330082f3e..5a444bb1d0ff 100644 --- a/core/jni/android_util_XmlBlock.cpp +++ b/core/jni/android_util_XmlBlock.cpp @@ -28,6 +28,9 @@ #include <stdio.h> namespace android { +constexpr int kNullDocument = UNEXPECTED_NULL; +// The reason not to ResXMLParser::BAD_DOCUMENT which is -1 is that other places use the same value. +constexpr int kBadDocument = BAD_VALUE; // ---------------------------------------------------------------------------- @@ -92,9 +95,7 @@ static jlong android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobjec return reinterpret_cast<jlong>(st); } -static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeNext(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { return ResXMLParser::END_DOCUMENT; @@ -121,14 +122,10 @@ static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, } while (true); bad: - jniThrowException(env, "org/xmlpull/v1/XmlPullParserException", - "Corrupt XML binary file"); - return ResXMLParser::BAD_DOCUMENT; + return kBadDocument; } -static jint android_content_XmlBlock_nativeGetNamespace(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetNamespace(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { return -1; @@ -137,9 +134,7 @@ static jint android_content_XmlBlock_nativeGetNamespace(JNIEnv* env, jobject cla return static_cast<jint>(st->getElementNamespaceID()); } -static jint android_content_XmlBlock_nativeGetName(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetName(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { return -1; @@ -148,9 +143,7 @@ static jint android_content_XmlBlock_nativeGetName(JNIEnv* env, jobject clazz, return static_cast<jint>(st->getElementNameID()); } -static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetText(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { return -1; @@ -159,97 +152,80 @@ static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz, return static_cast<jint>(st->getTextID()); } -static jint android_content_XmlBlock_nativeGetLineNumber(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetLineNumber(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getLineNumber()); } -static jint android_content_XmlBlock_nativeGetAttributeCount(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetAttributeCount( + CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeCount()); } -static jint android_content_XmlBlock_nativeGetAttributeNamespace(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeNamespace( + CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeNamespaceID(idx)); } -static jint android_content_XmlBlock_nativeGetAttributeName(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeName(CRITICAL_JNI_PARAMS_COMMA jlong token, + jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeNameID(idx)); } -static jint android_content_XmlBlock_nativeGetAttributeResource(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeResource( + CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeNameResID(idx)); } -static jint android_content_XmlBlock_nativeGetAttributeDataType(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeDataType( + CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeDataType(idx)); } -static jint android_content_XmlBlock_nativeGetAttributeData(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeData(CRITICAL_JNI_PARAMS_COMMA jlong token, + jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeData(idx)); } -static jint android_content_XmlBlock_nativeGetAttributeStringValue(JNIEnv* env, jobject clazz, - jlong token, jint idx) -{ +static jint android_content_XmlBlock_nativeGetAttributeStringValue( + CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } return static_cast<jint>(st->getAttributeValueStringID(idx)); @@ -286,39 +262,32 @@ static jint android_content_XmlBlock_nativeGetAttributeIndex(JNIEnv* env, jobjec return idx; } -static jint android_content_XmlBlock_nativeGetIdAttribute(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetIdAttribute(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } ssize_t idx = st->indexOfID(); return idx >= 0 ? static_cast<jint>(st->getAttributeValueStringID(idx)) : -1; } -static jint android_content_XmlBlock_nativeGetClassAttribute(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetClassAttribute( + CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } ssize_t idx = st->indexOfClass(); return idx >= 0 ? static_cast<jint>(st->getAttributeValueStringID(idx)) : -1; } -static jint android_content_XmlBlock_nativeGetStyleAttribute(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetStyleAttribute( + CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; + return kNullDocument; } ssize_t idx = st->indexOfStyle(); @@ -336,9 +305,7 @@ static jint android_content_XmlBlock_nativeGetStyleAttribute(JNIEnv* env, jobjec ? value.data : 0; } -static jint android_content_XmlBlock_nativeGetSourceResId(JNIEnv* env, jobject clazz, - jlong token) -{ +static jint android_content_XmlBlock_nativeGetSourceResId(CRITICAL_JNI_PARAMS_COMMA jlong token) { ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token); if (st == NULL) { return 0; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index aefec6cbc2cb..b11f22a030d9 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -28,6 +28,7 @@ #include <android_runtime/android_graphics_GraphicBuffer.h> #include <android_runtime/android_hardware_HardwareBuffer.h> #include <android_runtime/android_view_Surface.h> +#include <android_runtime/android_view_SurfaceControl.h> #include <android_runtime/android_view_SurfaceSession.h> #include <gui/ISurfaceComposer.h> #include <gui/Surface.h> @@ -221,8 +222,14 @@ static struct { static struct { jclass clazz; + jfieldID mNativeObject; +} gTransactionClassInfo; + +static struct { + jclass clazz; + jfieldID mNativeObject; jmethodID invokeReleaseCallback; -} gInvokeReleaseCallback; +} gSurfaceControlClassInfo; constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) { switch (colorMode) { @@ -511,10 +518,11 @@ static ReleaseBufferCallback genReleaseCallback(JNIEnv* env, jobject releaseCall if (fenceCopy) { fenceCopy->incStrong(0); } - globalCallbackRef->env()->CallStaticVoidMethod(gInvokeReleaseCallback.clazz, - gInvokeReleaseCallback.invokeReleaseCallback, - globalCallbackRef->object(), - reinterpret_cast<jlong>(fenceCopy)); + globalCallbackRef->env() + ->CallStaticVoidMethod(gSurfaceControlClassInfo.clazz, + gSurfaceControlClassInfo.invokeReleaseCallback, + globalCallbackRef->object(), + reinterpret_cast<jlong>(fenceCopy)); }; } @@ -1520,27 +1528,6 @@ static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->reparent(ctrl, newParent); } -static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject, - jintArray jHdrTypes) { - sp<IBinder> token(ibinderForJavaObject(env, tokenObject)); - if (token == nullptr || jHdrTypes == nullptr) return; - - int* hdrTypes = env->GetIntArrayElements(jHdrTypes, 0); - int numHdrTypes = env->GetArrayLength(jHdrTypes); - - std::vector<ui::Hdr> hdrTypesVector; - for (int i = 0; i < numHdrTypes; i++) { - hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i])); - } - env->ReleaseIntArrayElements(jHdrTypes, hdrTypes, 0); - - status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector); - if (error != NO_ERROR) { - jniThrowExceptionFmt(env, "java/lang/SecurityException", - "ACCESS_SURFACE_FLINGER is missing"); - } -} - static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) { bool isBootDisplayModeSupported = false; SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported); @@ -1906,6 +1893,28 @@ static jobject nativeGetDefaultApplyToken(JNIEnv* env, jclass clazz) { // ---------------------------------------------------------------------------- +SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env, + jobject surfaceControlObj) { + if (!!surfaceControlObj && + env->IsInstanceOf(surfaceControlObj, gSurfaceControlClassInfo.clazz)) { + return reinterpret_cast<SurfaceControl*>( + env->GetLongField(surfaceControlObj, gSurfaceControlClassInfo.mNativeObject)); + } else { + return nullptr; + } +} + +SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSurfaceTransaction( + JNIEnv* env, jobject surfaceTransactionObj) { + if (!!surfaceTransactionObj && + env->IsInstanceOf(surfaceTransactionObj, gTransactionClassInfo.clazz)) { + return reinterpret_cast<SurfaceComposerClient::Transaction*>( + env->GetLongField(surfaceTransactionObj, gTransactionClassInfo.mNativeObject)); + } else { + return nullptr; + } +} + static const JNINativeMethod sSurfaceControlMethods[] = { // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", @@ -2026,8 +2035,6 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetGameContentType }, {"nativeGetCompositionDataspaces", "()[I", (void*)nativeGetCompositionDataspaces}, - {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V", - (void*)nativeOverrideHdrTypes }, {"nativeClearContentFrameStats", "(J)Z", (void*)nativeClearContentFrameStats }, {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z", @@ -2306,11 +2313,18 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I"); jclass surfaceControlClazz = FindClassOrDie(env, "android/view/SurfaceControl"); - gInvokeReleaseCallback.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz); - gInvokeReleaseCallback.invokeReleaseCallback = + gSurfaceControlClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz); + gSurfaceControlClassInfo.mNativeObject = + GetFieldIDOrDie(env, gSurfaceControlClassInfo.clazz, "mNativeObject", "J"); + gSurfaceControlClassInfo.invokeReleaseCallback = GetStaticMethodIDOrDie(env, surfaceControlClazz, "invokeReleaseCallback", "(Ljava/util/function/Consumer;J)V"); + jclass surfaceTransactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction"); + gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceTransactionClazz); + gTransactionClassInfo.mNativeObject = + GetFieldIDOrDie(env, gTransactionClassInfo.clazz, "mNativeObject", "J"); + return err; } diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp index c1929c6535fb..3bada15aa834 100644 --- a/core/jni/android_window_ScreenCapture.cpp +++ b/core/jni/android_window_ScreenCapture.cpp @@ -61,8 +61,9 @@ static struct { } gLayerCaptureArgsClassInfo; static struct { - jmethodID accept; -} gConsumerClassInfo; + jclass clazz; + jmethodID onScreenCaptureComplete; +} gScreenCaptureListenerClassInfo; static struct { jclass clazz; @@ -97,14 +98,14 @@ class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener { public: explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) { env->GetJavaVM(&mVm); - mConsumerObject = env->NewGlobalRef(jobject); - LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref"); + mScreenCaptureListenerObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref"); } ~ScreenCaptureListenerWrapper() { - if (mConsumerObject) { - getenv()->DeleteGlobalRef(mConsumerObject); - mConsumerObject = nullptr; + if (mScreenCaptureListenerObject) { + getenv()->DeleteGlobalRef(mScreenCaptureListenerObject); + mScreenCaptureListenerObject = nullptr; } } @@ -112,8 +113,9 @@ public: const gui::ScreenCaptureResults& captureResults) override { JNIEnv* env = getenv(); if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) { - env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr); - checkAndClearException(env, "accept"); + env->CallVoidMethod(mScreenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); + checkAndClearException(env, "onScreenCaptureComplete"); return binder::Status::ok(); } captureResults.fenceResult.value()->waitForever(LOG_TAG); @@ -128,15 +130,17 @@ public: captureResults.capturedSecureLayers, captureResults.capturedHdrLayers); checkAndClearException(env, "builder"); - env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer); - checkAndClearException(env, "accept"); + env->CallVoidMethod(mScreenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, + screenshotHardwareBuffer); + checkAndClearException(env, "onScreenCaptureComplete"); env->DeleteLocalRef(jhardwareBuffer); env->DeleteLocalRef(screenshotHardwareBuffer); return binder::Status::ok(); } private: - jobject mConsumerObject; + jobject mScreenCaptureListenerObject; JavaVM* mVm; JNIEnv* getenv() { @@ -190,7 +194,7 @@ static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env, } static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject, - jlong screenCaptureListenerObject) { + jobject screenCaptureListenerObject) { const DisplayCaptureArgs captureArgs = displayCaptureArgsFromObject(env, displayCaptureArgsObject); @@ -198,13 +202,13 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu return BAD_VALUE; } - sp<gui::IScreenCaptureListener> captureListener = - reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject); + sp<IScreenCaptureListener> captureListener = + sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); return ScreenshotClient::captureDisplay(captureArgs, captureListener); } static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, - jlong screenCaptureListenerObject) { + jobject screenCaptureListenerObject) { LayerCaptureArgs captureArgs; getCaptureArgs(env, layerCaptureArgsObject, captureArgs); SurfaceControl* layer = reinterpret_cast<SurfaceControl*>( @@ -234,70 +238,21 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA } } - sp<gui::IScreenCaptureListener> captureListener = - reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject); + sp<IScreenCaptureListener> captureListener = + sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); return ScreenshotClient::captureLayers(captureArgs, captureListener); } -static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) { - sp<gui::IScreenCaptureListener> listener = - sp<ScreenCaptureListenerWrapper>::make(env, consumerObj); - listener->incStrong((void*)nativeCreateScreenCaptureListener); - return reinterpret_cast<jlong>(listener.get()); -} - -static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject, - jobject parcelObj) { - Parcel* parcel = parcelForJavaObject(env, parcelObj); - if (parcel == NULL) { - jniThrowNullPointerException(env, NULL); - return; - } - ScreenCaptureListenerWrapper* const self = - reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject); - if (self != nullptr) { - parcel->writeStrongBinder(IInterface::asBinder(self)); - } -} - -static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) { - Parcel* parcel = parcelForJavaObject(env, parcelObj); - if (parcel == NULL) { - jniThrowNullPointerException(env, NULL); - return 0; - } - sp<gui::IScreenCaptureListener> listener = - interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder()); - if (listener == nullptr) { - return 0; - } - listener->incStrong((void*)nativeCreateScreenCaptureListener); - return reinterpret_cast<jlong>(listener.get()); -} - -void destroyNativeListener(void* ptr) { - ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr); - listener->decStrong((void*)nativeCreateScreenCaptureListener); -} - -static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) { - return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener)); -} - // ---------------------------------------------------------------------------- static const JNINativeMethod sScreenCaptureMethods[] = { // clang-format off - {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I", + {"nativeCaptureDisplay", + "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", (void*)nativeCaptureDisplay }, - {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I", + {"nativeCaptureLayers", + "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", (void*)nativeCaptureLayers }, - {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J", - (void*)nativeCreateScreenCaptureListener }, - {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel }, - {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J", - (void*)nativeReadListenerFromParcel }, - {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer }, // clang-format on }; @@ -338,8 +293,12 @@ int register_android_window_ScreenCapture(JNIEnv* env) { gLayerCaptureArgsClassInfo.childrenOnly = GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z"); - jclass consumer = FindClassOrDie(env, "java/util/function/Consumer"); - gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V"); + jclass screenCaptureListenerClazz = + FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener"); + gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz); + gScreenCaptureListenerClassInfo.onScreenCaptureComplete = + GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete", + "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V"); jclass screenshotGraphicsBufferClazz = FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer"); diff --git a/core/jni/include/android_runtime/android_view_SurfaceControl.h b/core/jni/include/android_runtime/android_view_SurfaceControl.h new file mode 100644 index 000000000000..10a754903208 --- /dev/null +++ b/core/jni/include/android_runtime/android_view_SurfaceControl.h @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_VIEW_SURFACECONTROL_H +#define _ANDROID_VIEW_SURFACECONTROL_H + +#include <gui/SurfaceComposerClient.h> +#include <gui/SurfaceControl.h> + +#include "jni.h" + +namespace android { + +/* Gets the underlying native SurfaceControl for a java SurfaceControl. */ +extern SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl( + JNIEnv* env, jobject surfaceControlObj); + +/* Gets the underlying native SurfaceControl for a java SurfaceControl. */ +extern SurfaceComposerClient::Transaction* +android_view_SurfaceTransaction_getNativeSurfaceTransaction(JNIEnv* env, + jobject surfaceTransactionObj); + +} // namespace android + +#endif // _ANDROID_VIEW_SURFACECONTROL_H diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 4bbfee2f93cd..59e01bfa0c4b 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -61,7 +61,6 @@ import "frameworks/base/core/proto/android/util/textdump.proto"; import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/section.proto"; import "frameworks/base/proto/src/ipconnectivity.proto"; -import "packages/modules/Permission/service/proto/role_service.proto"; package android.os; @@ -358,10 +357,7 @@ message IncidentProto { (section).userdebug_and_eng_only = true ]; - optional com.android.role.RoleServiceDumpProto role = 3024 [ - (section).type = SECTION_DUMPSYS, - (section).args = "role --proto" - ]; + reserved 3024; optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [ (section).type = SECTION_DUMPSYS, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4d41c3091786..78c07107bb2a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6810,8 +6810,9 @@ android:exported="false"> </activity> - <activity android:name="com.android.server.logcat.LogAccessDialogActivity" + <activity android:name="com.android.internal.app.LogAccessDialogActivity" android:theme="@style/Theme.Translucent.NoTitleBar" + android:process=":ui" android:excludeFromRecents="true" android:exported="false"> </activity> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index a334461cab88..da132d9942df 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1857,8 +1857,8 @@ <string name="confirm_battery_saver" msgid="5247976246208245754">"D\'acord"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string> <string name="battery_saver_description" msgid="8518809702138617167">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string> - <string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Economitzador de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string> - <string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Economitzador de dades?"</string> + <string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Estalvi de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string> + <string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Estalvi de dades?"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"Activa"</string> <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Durant 1 minut (fins a les {formattedTime})}other{Durant # minuts (fins a les {formattedTime})}}"</string> <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Durant 1 min (fins a les {formattedTime})}other{Durant # min (fins a les {formattedTime})}}"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 82c716fa75c6..fdda0a70f4c3 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1491,8 +1491,8 @@ <string name="vpn_title_long" msgid="6834144390504619998">"VPN را <xliff:g id="APP">%s</xliff:g> فعال کرده است"</string> <string name="vpn_text" msgid="2275388920267251078">"برای مدیریت شبکه ضربه بزنید."</string> <string name="vpn_text_long" msgid="278540576806169831">"به <xliff:g id="SESSION">%s</xliff:g> متصل شد. برای مدیریت شبکه ضربه بزنید."</string> - <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"در حال اتصال VPN همیشه فعال…"</string> - <string name="vpn_lockdown_connected" msgid="2853127976590658469">"VPN همیشه فعال متصل شد"</string> + <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"درحال اتصال به VPN همیشه روشن…"</string> + <string name="vpn_lockdown_connected" msgid="2853127976590658469">"VPN همیشه روشن متصل شد"</string> <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"از «VPN همیشه روشن» قطع شد"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"به «VPN همیشه روشن» متصل نشد"</string> <string name="vpn_lockdown_config" msgid="8331697329868252169">"تغییر شبکه یا تنظیمات VPN"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 535996f8389d..cc393ab66533 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -72,7 +72,7 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string> - <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік деректер қызметі жоқ"</string> + <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік интернет қызметі жоқ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Жедел қызметке қоңырау шалу қолжетімді емес"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дауыстық қоңыраулар қызметі жоқ"</string> <string name="RestrictedOnAllVoiceTitle" msgid="3982069078579103087">"Дауыс қызметі немесе жедел қызметке қоңырау шалу мүмкіндігі жоқ"</string> @@ -85,7 +85,7 @@ <string name="notification_channel_network_alert" msgid="4788053066033851841">"Дабылдар"</string> <string name="notification_channel_call_forward" msgid="8230490317314272406">"Қоңырауды басқа нөмірге бағыттау"</string> <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Шұғыл кері қоңырау шалу режимі"</string> - <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Мобильдік деректер күйі"</string> + <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Мобильдік интернет күйі"</string> <string name="notification_channel_sms" msgid="1243384981025535724">"SMS хабарлары"</string> <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Дауыстық пошта хабарлары"</string> <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi қоңыраулары"</string> @@ -1306,7 +1306,7 @@ <string name="network_switch_metered_detail" msgid="1358296010128405906">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string> <string name="network_switch_metered_toast" msgid="501662047275723743">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string> <string-array name="network_switch_type_name"> - <item msgid="2255670471736226365">"мобильдік деректер"</item> + <item msgid="2255670471736226365">"мобильдік интернет"</item> <item msgid="5520925862115353992">"Wi-Fi"</item> <item msgid="1055487873974272842">"Bluetooth"</item> <item msgid="1616528372438698248">"Ethernet"</item> @@ -1573,7 +1573,7 @@ <string name="extract_edit_menu_button" msgid="63954536535863040">"Өзгерту"</string> <string name="data_usage_warning_title" msgid="9034893717078325845">"Дерек шығыны туралы ескерту"</string> <string name="data_usage_warning_body" msgid="1669325367188029454">"Деректің <xliff:g id="APP">%s</xliff:g> пайдаландыңыз"</string> - <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Мобильдік деректер шегіне жетті"</string> + <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Мобильдік интернет шегіне жетті"</string> <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Wi-Fi деректер шегіне жеттіңіз"</string> <string name="data_usage_limit_body" msgid="3567699582000085710">"Деректер жіберу қалған цикл үшін тоқтатылды"</string> <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Мобильдік дерек шегінен астыңыз"</string> @@ -1581,7 +1581,7 @@ <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Сіз <xliff:g id="SIZE">%s</xliff:g> шегінен асып кеттіңіз"</string> <string name="data_usage_restricted_title" msgid="126711424380051268">"Фондық деректер шектелген"</string> <string name="data_usage_restricted_body" msgid="5338694433686077733">"Шектеуді жою үшін түртіңіз."</string> - <string name="data_usage_rapid_title" msgid="2950192123248740375">"Мобильдік деректер көп жұмсалды"</string> + <string name="data_usage_rapid_title" msgid="2950192123248740375">"Мобильдік интернет көп жұмсалды"</string> <string name="data_usage_rapid_body" msgid="3886676853263693432">"Қолданбаларыңыз деректерді әдеттегіден көбірек пайдаланды"</string> <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"<xliff:g id="APP">%s</xliff:g> қолданбасы деректерді әдеттегіден көбірек пайдаланды"</string> <string name="ssl_certificate" msgid="5690020361307261997">"Қауіпсіздік сертификаты"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 7c3434544e84..791958b12df8 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -29,7 +29,7 @@ <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string> <string name="mmiError" msgid="2862759606579822246">"Problemă de conexiune sau cod MMI nevalid."</string> <string name="mmiFdnError" msgid="3975490266767565852">"Operația este limitată la numerele cu apelări restricționate."</string> - <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu puteți schimba setările de redirecționare a apelurilor de pe telefon când sunteți în roaming."</string> + <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu poți schimba setările de redirecționare a apelurilor de pe telefon când ești în roaming."</string> <string name="serviceEnabled" msgid="7549025003394765639">"Serviciul a fost activat."</string> <string name="serviceEnabledFor" msgid="1463104778656711613">"Serviciul a fost activat pentru:"</string> <string name="serviceDisabled" msgid="641878791205871379">"Serviciul a fost dezactivat."</string> @@ -44,7 +44,7 @@ <string name="invalidPuk" msgid="8831151490931907083">"Introdu un cod PUK care să aibă 8 cifre sau mai mult."</string> <string name="needPuk" msgid="7321876090152422918">"Cardul SIM este blocat cu codul PUK. Introdu codul PUK pentru a-l debloca."</string> <string name="needPuk2" msgid="7032612093451537186">"Introdu codul PUK2 pentru a debloca cardul SIM."</string> - <string name="enablePin" msgid="2543771964137091212">"Operațiunea nu a reușit. Activați blocarea cardului SIM/RUIM."</string> + <string name="enablePin" msgid="2543771964137091212">"Operațiunea nu a reușit. Activează blocarea cardului SIM/RUIM."</string> <plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584"> <item quantity="few">V-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> încercări până la blocarea cardului SIM.</item> <item quantity="other">V-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> de încercări până la blocarea cardului SIM.</item> @@ -66,13 +66,13 @@ <string name="ThreeWCMmi" msgid="2436550866139999411">"Apelare de tip conferință"</string> <string name="RuacMmi" msgid="1876047385848991110">"Respingere apeluri supărătoare nedorite"</string> <string name="CndMmi" msgid="185136449405618437">"Se apelează serviciul de furnizare a numerelor"</string> - <string name="DndMmi" msgid="8797375819689129800">"Nu deranjați"</string> + <string name="DndMmi" msgid="8797375819689129800">"Nu deranja"</string> <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: restricționat"</string> <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: nerestricționat"</string> <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: Restricționat."</string> <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string> - <string name="CLIRPermanent" msgid="166443681876381118">"Nu puteți să modificați setarea pentru ID-ul apelantului."</string> + <string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Fără serviciu de date mobile"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Apelurile de urgență nu sunt disponibile"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Fără servicii vocale"</string> @@ -120,7 +120,7 @@ <string name="roamingTextSearching" msgid="5323235489657753486">"Se caută serviciul"</string> <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Nu s-a putut configura apelarea prin Wi-Fi"</string> <string-array name="wfcOperatorErrorAlertMessages"> - <item msgid="468830943567116703">"Pentru a efectua apeluri și a trimite mesaje prin Wi-Fi, mai întâi solicitați configurarea acestui serviciu la operator. Apoi, activați din nou apelarea prin Wi-Fi din Setări. (Cod de eroare: <xliff:g id="CODE">%1$s</xliff:g>)"</item> + <item msgid="468830943567116703">"Pentru a face apeluri și a trimite mesaje prin Wi-Fi, mai întâi solicită configurarea acestui serviciu la operator. Apoi, activează din nou apelarea prin Wi-Fi din Setări. (Cod de eroare: <xliff:g id="CODE">%1$s</xliff:g>)"</item> </string-array> <string-array name="wfcOperatorErrorNotificationMessages"> <item msgid="4795145070505729156">"A apărut o problemă la înregistrarea apelării prin Wi‑Fi la operatorul dvs.: <xliff:g id="CODE">%1$s</xliff:g>"</item> @@ -139,7 +139,7 @@ <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Apelare prin Wi-Fi"</string> <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string> <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Dezactivată"</string> - <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Apelați prin Wi-Fi"</string> + <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Apelează prin Wi-Fi"</string> <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Apelați prin rețeaua mobilă"</string> <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string> <!-- no translation found for crossSimFormat_spn (9125246077491634262) --> @@ -178,7 +178,7 @@ <string name="low_memory" product="default" msgid="2539532364144025569">"Stocarea pe telefon este plină. Șterge câteva fișiere pentru a elibera spațiu."</string> <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{S-a instalat o autoritate de certificare}few{S-au instalat autorități de certificare}other{S-au instalat autorități de certificare}}"</string> <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"De o terță parte necunoscută"</string> - <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"De administratorul profilului dvs. de serviciu"</string> + <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"De administratorul profilului de serviciu"</string> <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"De <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string> <string name="work_profile_deleted" msgid="5891181538182009328">"Profilul de serviciu a fost șters"</string> <string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplicația de administrare a profilului de serviciu lipsește sau este deteriorată. Prin urmare, profilul de serviciu și datele asociate au fost șterse. Pentru asistență, contactați administratorul."</string> @@ -188,7 +188,7 @@ <string name="network_logging_notification_title" msgid="554983187553845004">"Dispozitivul este gestionat"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizația dvs. gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atingeți pentru mai multe detalii."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplicațiile vă pot accesa locația"</string> - <string name="location_changed_notification_text" msgid="7158423339982706912">"Contactați administratorul IT pentru a afla mai multe"</string> + <string name="location_changed_notification_text" msgid="7158423339982706912">"Contactează administratorul IT pentru a afla mai multe"</string> <string name="geofencing_service" msgid="3826902410740315456">"Serviciul de delimitare geografică"</string> <string name="country_detector" msgid="7023275114706088854">"Detector de țară"</string> <string name="location_service" msgid="2439187616018455546">"Servicii de localizare"</string> @@ -201,18 +201,18 @@ <string name="factory_reset_warning" msgid="6858705527798047809">"Datele de pe dispozitiv vor fi șterse"</string> <string name="factory_reset_message" msgid="2657049595153992213">"Aplicația de administrare nu poate fi utilizată. Dispozitivul va fi șters.\n\nDacă aveți întrebări, contactați administratorul organizației dvs."</string> <string name="printing_disabled_by" msgid="3517499806528864633">"Printare dezactivată de <xliff:g id="OWNER_APP">%s</xliff:g>."</string> - <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Activați profilul de serviciu"</string> + <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Activează profilul de serviciu"</string> <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Aplicațiile personale sunt blocate până când activați profilul de serviciu"</string> <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"Aplicațiile personale vor fi blocate pe <xliff:g id="DATE">%1$s</xliff:g>, la <xliff:g id="TIME">%2$s</xliff:g>. Administratorul IT nu permite ca profilul de serviciu să fie dezactivat mai mult de <xliff:g id="NUMBER">%3$d</xliff:g> zile."</string> - <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Activați"</string> + <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Activează"</string> <string name="me" msgid="6207584824693813140">"Eu"</string> <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Opțiuni tablet PC"</string> <string name="power_dialog" product="tv" msgid="7792839006640933763">"Opțiuni pentru Android TV"</string> <string name="power_dialog" product="default" msgid="1107775420270203046">"Opțiuni telefon"</string> <string name="silent_mode" msgid="8796112363642579333">"Mod Silențios"</string> - <string name="turn_on_radio" msgid="2961717788170634233">"Activați funcția wireless"</string> - <string name="turn_off_radio" msgid="7222573978109933360">"Dezactivați funcția wireless"</string> - <string name="screen_lock" msgid="2072642720826409809">"Blocați ecranul"</string> + <string name="turn_on_radio" msgid="2961717788170634233">"Activează funcția wireless"</string> + <string name="turn_off_radio" msgid="7222573978109933360">"Dezactivează funcția wireless"</string> + <string name="screen_lock" msgid="2072642720826409809">"Blochează ecranul"</string> <string name="power_off" msgid="4111692782492232778">"Oprește"</string> <string name="silent_mode_silent" msgid="5079789070221150912">"Sonerie dezactivată"</string> <string name="silent_mode_vibrate" msgid="8821830448369552678">"Vibrare sonerie"</string> @@ -230,26 +230,26 @@ <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefonul dvs. se va închide."</string> <string name="shutdown_confirm_question" msgid="796151167261608447">"Doriți să închideți?"</string> <string name="reboot_safemode_title" msgid="5853949122655346734">"Repornește în modul sigur"</string> - <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Doriți să reporniți în modul sigur? Astfel vor fi dezactivate toate aplicațiile terță parte pe care le-ați instalat. Acestea vor fi restabilite când reporniți din nou."</string> + <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Repornești în modul sigur? Astfel vor fi dezactivate toate aplicațiile terță parte instalate. Acestea vor fi restabilite când repornești dispozitivul."</string> <string name="recent_tasks_title" msgid="8183172372995396653">"Recente"</string> <string name="no_recent_tasks" msgid="9063946524312275906">"Nu există aplicații recente."</string> <string name="global_actions" product="tablet" msgid="4412132498517933867">"Opțiuni tablet PC"</string> <string name="global_actions" product="tv" msgid="3871763739487450369">"Opțiuni pentru Android TV"</string> <string name="global_actions" product="default" msgid="6410072189971495460">"Opțiuni telefon"</string> - <string name="global_action_lock" msgid="6949357274257655383">"Blocați ecranul"</string> + <string name="global_action_lock" msgid="6949357274257655383">"Blochează ecranul"</string> <string name="global_action_power_off" msgid="4404936470711393203">"Oprește"</string> <string name="global_action_power_options" msgid="1185286119330160073">"Alimentare"</string> <string name="global_action_restart" msgid="4678451019561687074">"Repornește"</string> <string name="global_action_emergency" msgid="1387617624177105088">"Urgență"</string> <string name="global_action_bug_report" msgid="5127867163044170003">"Raport despre erori"</string> - <string name="global_action_logout" msgid="6093581310002476511">"Încheiați sesiunea"</string> + <string name="global_action_logout" msgid="6093581310002476511">"Încheie sesiunea"</string> <string name="global_action_screenshot" msgid="2610053466156478564">"Instantaneu"</string> <string name="bugreport_title" msgid="8549990811777373050">"Raport de eroare"</string> <string name="bugreport_message" msgid="5212529146119624326">"Acest raport va colecta informații despre starea actuală a dispozitivului, pentru a le trimite într-un e-mail. Aveți răbdare după pornirea raportului despre erori până când va fi gata de trimis."</string> <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Raport interactiv"</string> - <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Folosiți această opțiune în majoritatea situațiilor. Astfel, puteți să urmăriți progresul raportului, să introduceți mai multe detalii în privința problemei și să creați capturi de ecran. Pot fi omise unele secțiuni mai puțin folosite pentru care raportarea durează prea mult."</string> + <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Folosește această opțiune în majoritatea situațiilor. Astfel, poți să urmărești progresul raportului, să introduci mai multe detalii în privința problemei și să creezi capturi de ecran. Pot fi omise unele secțiuni mai puțin folosite pentru care raportarea durează prea mult."</string> <string name="bugreport_option_full_title" msgid="7681035745950045690">"Raport complet"</string> - <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Folosiți această opțiune pentru a reduce la minimum interferențele cu sistemul când dispozitivul nu răspunde, funcționează prea lent sau când aveți nevoie de toate secțiunile raportului. Nu puteți să introduceți mai multe detalii sau să creați capturi de ecran suplimentare."</string> + <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Folosește această opțiune pentru a reduce la minimum interferențele cu sistemul când dispozitivul nu răspunde, funcționează prea lent sau când ai nevoie de toate secțiunile raportului. Nu poți să introduci mai multe detalii sau să creezi capturi de ecran suplimentare."</string> <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Peste # secundă se va realiza o captură de ecran pentru raportul de eroare.}few{Peste # secunde se va realiza o captură de ecran pentru raportul de eroare.}other{Peste # de secunde se va realiza o captură de ecran pentru raportul de eroare.}}"</string> <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"S-a realizat captura de ecran a raportului de eroare"</string> <string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Nu s-a realizat captura de ecran a raportului de eroare"</string> @@ -294,9 +294,9 @@ <string name="user_owner_label" msgid="8628726904184471211">"Comutați la profilul personal"</string> <string name="managed_profile_label" msgid="7316778766973512382">"Comutați la profilul de serviciu"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Agendă"</string> - <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acceseze persoanele de contact"</string> + <string name="permgroupdesc_contacts" msgid="9163927941244182567">"să acceseze agenda"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"Locație"</string> - <string name="permgroupdesc_location" msgid="1995955142118450685">"acceseze locația acestui dispozitiv"</string> + <string name="permgroupdesc_location" msgid="1995955142118450685">"să acceseze locația acestui dispozitiv"</string> <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string> <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceseze calendarul"</string> <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string> @@ -310,7 +310,7 @@ <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfon"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"înregistreze sunet"</string> <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Activitate fizică"</string> - <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"accesați activitatea fizică"</string> + <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"să acceseze activitatea fizică"</string> <string name="permgrouplab_camera" msgid="9090413408963547706">"Camera foto"</string> <string name="permgroupdesc_camera" msgid="7585150538459320326">"fotografieze și să înregistreze videoclipuri"</string> <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Dispozitive din apropiere"</string> @@ -318,14 +318,14 @@ <string name="permgrouplab_calllog" msgid="7926834372073550288">"Jurnale de apeluri"</string> <string name="permgroupdesc_calllog" msgid="2026996642917801803">"să citească și să scrie jurnalul de apeluri telefonice"</string> <string name="permgrouplab_phone" msgid="570318944091926620">"Telefon"</string> - <string name="permgroupdesc_phone" msgid="270048070781478204">"inițieze și să gestioneze apeluri telefonice"</string> + <string name="permgroupdesc_phone" msgid="270048070781478204">"să inițieze și să gestioneze apeluri telefonice"</string> <string name="permgrouplab_sensors" msgid="9134046949784064495">"Senzori corporali"</string> - <string name="permgroupdesc_sensors" msgid="2610631290633747752">"acceseze datele de la senzori despre semnele vitale"</string> + <string name="permgroupdesc_sensors" msgid="2610631290633747752">"să acceseze datele de la senzori despre semnele vitale"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notificări"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"să afișeze notificări"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Analizeze conținutul ferestrei"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspectează conținutul unei ferestre cu care interacționați."</string> - <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activeze funcția Explorați prin atingere"</string> + <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"să activeze funcția Explorează prin atingere"</string> <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Elementele atinse vor fi rostite cu voce tare, iar ecranul poate fi explorat utilizând gesturi."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Remarce textul pe care îl introduceți"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Include date personale, cum ar fi numere ale cardurilor de credit sau parole."</string> @@ -356,17 +356,17 @@ <string name="permlab_receiveSms" msgid="505961632050451881">"primește mesaje text (SMS)"</string> <string name="permdesc_receiveSms" msgid="1797345626687832285">"Permite aplicației să primească și să proceseze mesaje SMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string> <string name="permlab_receiveMms" msgid="4000650116674380275">"primește mesaje text (MMS)"</string> - <string name="permdesc_receiveMms" msgid="958102423732219710">"Permite aplicației să primească și să proceseze mesaje MMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string> + <string name="permdesc_receiveMms" msgid="958102423732219710">"Permite aplicației să primească și să proceseze mesaje MMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitiv fără a ți le arăta."</string> <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Redirecționează mesajele cu transmisie celulară"</string> <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite aplicației să se conecteze la modulul de transmisie celulară pentru a redirecționa mesajele cu transmisie celulară pe măsură ce le primește. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgență."</string> <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Să gestioneze apelurile în desfășurare"</string> <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite unei aplicații să vadă detalii despre apelurile în desfășurare de pe dispozitiv și să gestioneze apelurile respective."</string> <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"citește mesajele cu transmisie celulară"</string> - <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitivul dvs. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgență."</string> + <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitiv. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a te avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului când e primită o transmisie celulară de urgență."</string> <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"citire feeduri abonat"</string> <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Permite aplicației să obțină detalii despre feedurile sincronizate în prezent."</string> - <string name="permlab_sendSms" msgid="7757368721742014252">"trimită și să vadă mesajele SMS"</string> - <string name="permdesc_sendSms" msgid="6757089798435130769">"Permite aplicației să trimită mesaje SMS, ceea ce ar putea determina apariția unor taxe neașteptate. Aplicațiile rău intenționate pot acumula costuri prin trimiterea mesajelor fără confirmarea dvs."</string> + <string name="permlab_sendSms" msgid="7757368721742014252">"să trimită și să vadă mesajele SMS"</string> + <string name="permdesc_sendSms" msgid="6757089798435130769">"Permite aplicației să trimită mesaje SMS, ceea ce ar putea duce la costuri neașteptate. Aplicațiile rău intenționate pot acumula costuri prin trimiterea mesajelor fără confirmarea ta."</string> <string name="permlab_readSms" msgid="5164176626258800297">"citește mesajele text (SMS sau MMS)"</string> <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Această aplicație poate citi toate mesajele SMS stocate pe tabletă."</string> <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Această aplicație poate să citească toate mesajele SMS (texT) stocate pe dispozitivul Android TV."</string> @@ -378,10 +378,10 @@ <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"să gestioneze profilul și proprietarii dispozitivului"</string> <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Permite aplicațiilor să seteze proprietarii de profiluri și proprietarul dispozitivului."</string> <string name="permlab_reorderTasks" msgid="7598562301992923804">"reordonare aplicații care rulează"</string> - <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Permite aplicației să mute activitățile în prim-plan și în fundal. Aplicația poate face acest lucru fără aportul dvs."</string> + <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Permite aplicației să mute activitățile în prim-plan și în fundal. Aplicația poate face acest lucru fără intervenția ta."</string> <string name="permlab_enableCarMode" msgid="893019409519325311">"activare mod Mașină"</string> <string name="permdesc_enableCarMode" msgid="56419168820473508">"Permite aplicației să activeze modul Mașină."</string> - <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"închide alte aplicații"</string> + <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"să închidă alte aplicații"</string> <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Permite aplicației să oprească procesele derulate în fundal de alte aplicații. Acest lucru poate face ca respectivele aplicații să nu mai ruleze."</string> <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Această aplicație poate apărea deasupra altor aplicații"</string> <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Această aplicație poate apărea deasupra altor aplicații sau a altor părți ale ecranului. Acest lucru poate să afecteze utilizarea normală a aplicației și să schimbe modul în care se afișează alte aplicații."</string> @@ -418,7 +418,7 @@ <string name="permlab_readCallLog" msgid="1739990210293505948">"citește jurnalul de apeluri"</string> <string name="permdesc_readCallLog" msgid="8964770895425873433">"Această aplicație poate citi istoricul apelurilor."</string> <string name="permlab_writeCallLog" msgid="670292975137658895">"scrie jurnalul de apeluri"</string> - <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Permite aplicației să modifice jurnalul de apeluri al tabletei dvs., inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul dvs. de apeluri."</string> + <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Permite aplicației să modifice jurnalul de apeluri al tabletei, inclusiv datele despre apelurile primite sau făcute. Aplicațiile rău intenționate pot folosi această permisiune pentru a șterge sau a modifica jurnalul de apeluri."</string> <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Permite aplicației să modifice jurnalul de apeluri al dispozitivului Android TV, inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul de apeluri."</string> <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Permite aplicației să modifice jurnalul de apeluri al telefonului dvs., inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul dvs. de apeluri."</string> <string name="permlab_bodySensors" msgid="662918578601619569">"Să acceseze date de la senzorii corporali, cum ar fi pulsul, în timpul folosirii"</string> @@ -443,7 +443,7 @@ <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Aplicația poate accesa locația oricând, chiar dacă nu este folosită."</string> <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modificare setări audio"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite aplicației să modifice setările audio globale, cum ar fi volumul și difuzorul care este utilizat pentru ieșire."</string> - <string name="permlab_recordAudio" msgid="1208457423054219147">"înregistreze sunet"</string> + <string name="permlab_recordAudio" msgid="1208457423054219147">"să înregistreze sunet"</string> <string name="permdesc_recordAudio" msgid="5857246765327514062">"Această aplicație poate să înregistreze conținut audio folosind microfonul când este în uz."</string> <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"să înregistreze conținut audio în fundal"</string> <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Această aplicație poate înregistra conținut audio folosind microfonul oricând."</string> @@ -462,7 +462,7 @@ <string name="permlab_vibrate" msgid="8596800035791962017">"controlează vibrarea"</string> <string name="permdesc_vibrate" msgid="8733343234582083721">"Permite aplicației să controleze mecanismul de vibrare."</string> <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Permite aplicației să acceseze modul de vibrații."</string> - <string name="permlab_callPhone" msgid="1798582257194643320">"apelare directă numere de telefon"</string> + <string name="permlab_callPhone" msgid="1798582257194643320">"să sune direct la numere de telefon"</string> <string name="permdesc_callPhone" msgid="5439809516131609109">"Permite aplicației să apeleze numere de telefon fără intervenția dvs. Acest lucru poate determina apariția unor taxe sau a unor apeluri neașteptate. Cu această permisiune aplicația nu poate apela numerele de urgență. Aplicațiile rău intenționate pot acumula costuri prin efectuarea unor apeluri fără confirmare."</string> <string name="permlab_accessImsCallService" msgid="442192920714863782">"accesează serviciul de apelare IMS"</string> <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite aplicației să folosească serviciul IMS pentru apeluri, fără intervenția dvs."</string> @@ -472,7 +472,7 @@ <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite ca aplicația să acceseze funcțiile de telefonie de bază ale dispozitivului."</string> <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"să direcționeze apelurile prin intermediul sistemului"</string> <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite aplicației să direcționeze apelurile prin intermediul sistemului pentru a îmbunătăți calitatea apelurilor."</string> - <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Vedeți și controlați apelurile prin intermediul sistemului."</string> + <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Vezi și controlează apelurile prin intermediul sistemului."</string> <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Permite aplicației să vadă și să controleze apelurile în desfășurare pe dispozitiv. Aceasta include informații ca numerele pentru apeluri și starea apelurilor."</string> <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"scutită de restricțiile pentru înregistrarea conținutului audio"</string> <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Scutiți aplicația de restricțiile pentru înregistrarea conținutului audio."</string> @@ -501,10 +501,10 @@ <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Permite aplicației să schimbe fusul orar al dispozitivului Android TV."</string> <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Permite aplicației să schimbe fusul orar al telefonului."</string> <string name="permlab_getAccounts" msgid="5304317160463582791">"găsește conturi pe dispozitiv"</string> - <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Permite aplicației să obțină lista de conturi cunoscute de tabletă. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string> + <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Permite aplicației să obțină lista de conturi cunoscute de tabletă. Aceasta poate include conturile create de aplicațiile pe care le-ai instalat."</string> <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Permite aplicației să obțină lista conturilor cunoscute de dispozitivul Android TV. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string> <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Permite aplicației să obțină lista de conturi cunoscute de telefon. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string> - <string name="permlab_accessNetworkState" msgid="2349126720783633918">"vizualizează conexiunile la rețea"</string> + <string name="permlab_accessNetworkState" msgid="2349126720783633918">"să vadă conexiunile la rețea"</string> <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Permite aplicației să vadă informațiile despre conexiunile la rețea, cum ar fi rețelele existente și cele care sunt conectate."</string> <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"să aibă acces deplin la rețea"</string> <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Permite aplicației să creeze socluri de rețea și să utilizeze protocoale de rețea personalizate. Browserul și alte aplicații oferă mijloacele de trimitere a datelor pe internet, astfel încât această permisiune nu este necesară pentru trimiterea datelor pe internet."</string> @@ -512,7 +512,7 @@ <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Permite aplicației să modifice starea de conectivitate la rețea."</string> <string name="permlab_changeTetherState" msgid="9079611809931863861">"modificare conectivitate tethering"</string> <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Permite aplicației să modifice starea de conectivitate prin tethering la rețea."</string> - <string name="permlab_accessWifiState" msgid="5552488500317911052">"vizualizează conexiunile Wi-Fi"</string> + <string name="permlab_accessWifiState" msgid="5552488500317911052">"să vadă conexiunile Wi-Fi"</string> <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Permite aplicației să vadă informațiile despre rețelele Wi-Fi, de ex. dacă o rețea Wi-Fi este activată, precum și numele dispozitivelor conectate la rețeaua Wi-Fi."</string> <string name="permlab_changeWifiState" msgid="7947824109713181554">"se conectează și se deconectează de la Wi-Fi"</string> <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Permite aplicației să se conecteze și să se deconecteze de la punctele de acces Wi-Fi, precum și să efectueze modificări în configurația dispozitivului pentru rețelele Wi-Fi."</string> @@ -526,12 +526,12 @@ <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Permite aplicației să configureze telefonul Bluetooth local, să descopere și să se împerecheze cu dispozitive la distanță."</string> <string name="permlab_accessWimaxState" msgid="7029563339012437434">"se conectează și se deconectează de la WiMAX"</string> <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Permite aplicației să stabilească dacă o rețea WiMAX este activată și să vadă informațiile cu privire la toate rețelele WiMAX conectate."</string> - <string name="permlab_changeWimaxState" msgid="6223305780806267462">"schimbați starea WiMAX"</string> + <string name="permlab_changeWimaxState" msgid="6223305780806267462">"schimbă starea WiMAX"</string> <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Permite aplicației să conecteze și să deconecteze tableta la și de la rețelele WiMAX."</string> <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Permite aplicației să conecteze și să deconecteze dispozitivul Android TV de la rețelele WiMAX."</string> <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Permite aplicației să conecteze și să deconecteze telefonul la și de la rețelele WiMAX."</string> <string name="permlab_bluetooth" msgid="586333280736937209">"conectează dispozitive Bluetooth"</string> - <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite aplicației să vadă configurația tabletei Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string> + <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite aplicației să vadă configurația tabletei Bluetooth, să facă și să accepte conexiuni cu dispozitive asociate."</string> <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite aplicației să vadă configurația conexiunii prin Bluetooth a dispozitivului Android TV, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string> <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite aplicației să vadă configurația telefonului Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string> <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"să descopere și să asocieze dispozitive Bluetooth din apropiere"</string> @@ -563,44 +563,44 @@ <string name="permlab_useFingerprint" msgid="1001421069766751922">"folosește hardware-ul pentru amprentă"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite aplicației să folosească hardware pentru amprentă pentru autentificare"</string> <string name="permlab_audioWrite" msgid="8501705294265669405">"modificați colecția de muzică"</string> - <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite aplicației să vă modifice colecția de muzică."</string> + <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite aplicației să modifice colecția de muzică."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"modificați colecția de videoclipuri"</string> <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite aplicației să vă modifice colecția de videoclipuri."</string> <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificați colecția de fotografii"</string> <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Permite aplicației să vă modifice colecția de fotografii."</string> <string name="permlab_mediaLocation" msgid="7368098373378598066">"citiți locațiile din colecția media"</string> <string name="permdesc_mediaLocation" msgid="597912899423578138">"Permite aplicației să citească locațiile din colecția dvs. media."</string> - <string name="biometric_app_setting_name" msgid="3339209978734534457">"Folosiți sistemele biometrice"</string> - <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Folosiți sistemele biometrice sau blocarea ecranului"</string> + <string name="biometric_app_setting_name" msgid="3339209978734534457">"Folosește sistemele biometrice"</string> + <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Folosește sistemele biometrice sau blocarea ecranului"</string> <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmați-vă identitatea"</string> - <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Folosiți sistemele biometrice pentru a continua"</string> - <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Folosiți sistemele biometrice sau blocarea ecranului pentru a continua"</string> + <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Folosește sistemele biometrice pentru a continua"</string> + <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Folosește sistemele biometrice sau blocarea ecranului pentru a continua"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometric indisponibil"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentificarea a fost anulată"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"Nu este recunoscut"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentificarea a fost anulată"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nu este setat niciun cod PIN, model sau parolă"</string> <string name="biometric_error_generic" msgid="6784371929985434439">"Eroare la autentificare"</string> - <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosiți blocarea ecranului"</string> + <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosește blocarea ecranului"</string> <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduceți blocarea ecranului ca să continuați"</string> <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Apasă ferm pe senzor"</string> <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Amprenta nu a fost recunoscută. Încearcă din nou."</string> <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Curățați senzorul de amprentă și încercați din nou"</string> <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Curățați senzorul și încercați din nou"</string> <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Apasă ferm pe senzor"</string> - <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Ați mișcat degetul prea lent. Încercați din nou."</string> + <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Ai mișcat degetul prea lent. Încearcă din nou."</string> <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Încearcă altă amprentă"</string> <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Prea luminos"</string> <string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"S-a detectat apăsarea butonului de alimentare"</string> <string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Încercați să ajustați"</string> - <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Schimbați ușor poziția degetului de fiecare dată"</string> + <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Schimbă ușor poziția degetului de fiecare dată"</string> <string-array name="fingerprint_acquired_vendor"> </string-array> <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Amprenta nu a fost recunoscută"</string> <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Amprenta nu a fost recunoscută"</string> <string name="fingerprint_authenticated" msgid="2024862866860283100">"Amprentă autentificată"</string> <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Chip autentificat"</string> - <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Chip autentificat, apăsați Confirmați"</string> + <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Chip autentificat, apasă pe Confirmă"</string> <string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Hardware-ul pentru amprentă nu este disponibil."</string> <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Nu se poate configura amprenta"</string> <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Configurarea amprentei a expirat. Încearcă din nou."</string> @@ -615,10 +615,10 @@ <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Nu se poate folosi senzorul de amprentă. Vizitați un furnizor de servicii de reparații."</string> <string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"A fost apăsat butonul de pornire"</string> <string name="fingerprint_name_template" msgid="8941662088160289778">"Degetul <xliff:g id="FINGERID">%d</xliff:g>"</string> - <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Folosiți amprenta"</string> - <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Folosiți amprenta sau blocarea ecranului"</string> - <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Folosiți amprenta pentru a continua"</string> - <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Folosiți amprenta sau blocarea ecranului pentru a continua"</string> + <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Folosește amprenta"</string> + <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Folosește amprenta sau blocarea ecranului"</string> + <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Folosește amprenta pentru a continua"</string> + <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Folosește amprenta sau blocarea ecranului pentru a continua"</string> <string-array name="fingerprint_error_vendor"> </string-array> <string name="fingerprint_error_vendor_unknown" msgid="4170002184907291065">"A apărut o eroare. Încearcă din nou."</string> @@ -646,9 +646,9 @@ <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Priviți mai direct spre dispozitiv."</string> <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nu vi se vede fața. Țineți telefonul la nivelul ochilor."</string> <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Prea multă mișcare. Țineți telefonul nemișcat."</string> - <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrați-vă chipul."</string> + <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrează-ți chipul."</string> <string name="face_acquired_too_different" msgid="2520389515612972889">"Chipul nu a fost recunoscut. Reîncearcă."</string> - <string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbați ușor poziția capului"</string> + <string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbă ușor poziția capului"</string> <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Priviți direct spre telefon"</string> <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Priviți direct spre telefon"</string> <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"Priviți direct spre telefon"</string> @@ -670,21 +670,21 @@ <string name="face_error_user_canceled" msgid="5766472033202928373">"Deblocarea facială a fost anulată de utilizator"</string> <string name="face_error_lockout" msgid="7864408714994529437">"Prea multe încercări. Reîncearcă mai târziu."</string> <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Prea multe încercări. Deblocarea facială este dezactivată."</string> - <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Prea multe încercări. Folosiți blocarea ecranului."</string> + <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Prea multe încercări. Folosește blocarea ecranului."</string> <string name="face_error_unable_to_process" msgid="5723292697366130070">"Nu se poate confirma fața. Încearcă din nou."</string> <string name="face_error_not_enrolled" msgid="1134739108536328412">"Nu ați configurat Deblocarea facială"</string> <string name="face_error_hw_not_present" msgid="7940978724978763011">"Deblocarea facială nu este acceptată pe acest dispozitiv"</string> <string name="face_error_security_update_required" msgid="5076017208528750161">"Senzorul este dezactivat temporar."</string> <string name="face_name_template" msgid="3877037340223318119">"Chip <xliff:g id="FACEID">%d</xliff:g>"</string> - <string name="face_app_setting_name" msgid="5854024256907828015">"Folosiți Deblocarea facială"</string> - <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Folosiți deblocarea facială sau ecranul de blocare"</string> + <string name="face_app_setting_name" msgid="5854024256907828015">"Folosește Deblocarea facială"</string> + <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Folosește deblocarea facială sau ecranul de blocare"</string> <string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Folosiți-vă chipul ca să continuați"</string> <string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Folosiți-vă chipul sau blocarea ecranului pentru a continua"</string> <string-array name="face_error_vendor"> </string-array> <string name="face_error_vendor_unknown" msgid="7387005932083302070">"A apărut o eroare. Încearcă din nou."</string> <string name="face_icon_content_description" msgid="465030547475916280">"Pictograma chip"</string> - <string name="permlab_readSyncSettings" msgid="6250532864893156277">"citire setări sincronizare"</string> + <string name="permlab_readSyncSettings" msgid="6250532864893156277">"să citească setări sincronizare"</string> <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Permite aplicației să citească setările de sincronizare ale unui cont. De exemplu, cu această permisiune aplicația poate determina dacă aplicația Persoane este sincronizată cu un anumit cont."</string> <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"activează/dezactivează sincronizarea"</string> <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Permite unei aplicații să modifice setările de sincronizare ale unui cont. De exemplu, cu această permisiune aplicația poate activa sincronizarea aplicației Persoane cu un anumit cont."</string> @@ -711,7 +711,7 @@ <string name="permlab_bind_incall_service" msgid="5990625112603493016">"interacțiune cu ecranul în timpul unui apel"</string> <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Permite aplicației să controleze când și cum vede utilizatorul ecranul în timpul unui apel."</string> <string name="permlab_bind_connection_service" msgid="5409268245525024736">"să interacționeze cu servicii de telefonie"</string> - <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Permite aplicației să interacționeze cu servicii de telefonie pentru a da / a primi apeluri."</string> + <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Permite aplicației să interacționeze cu servicii de telefonie pentru a face / a primi apeluri."</string> <string name="permlab_control_incall_experience" msgid="6436863486094352987">"oferă o experiență de utilizare în timpul unui apel"</string> <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Permite aplicației să ofere o experiență de utilizare în timpul unui apel."</string> <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"citește utilizarea statistică a rețelei"</string> @@ -746,7 +746,7 @@ <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite aplicației să se conecteze la serviciile operatorului. Nu ar trebui să fie niciodată necesară pentru aplicațiile obișnuite."</string> <string name="permlab_access_notification_policy" msgid="5524112842876975537">"accesează Nu deranja"</string> <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite aplicației să citească și să scrie configurația Nu deranja."</string> - <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"porniți folosirea permisiunii de vizualizare"</string> + <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"să înceapă folosirea permisiunii de vizualizare"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite proprietarului să pornească folosirea permisiunii pentru o aplicație. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"să înceapă să examineze deciziile privind permisiunile"</string> <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite proprietarului să deschidă ecranul pentru a examina deciziile privind permisiunile. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> @@ -760,36 +760,36 @@ <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați tableta sau ștergeți datele acesteia dacă sunt introduse prea multe parole incorecte."</string> <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați dispozitivul Android TV sau ștergeți toate datele de pe acesta dacă se introduc prea multe parole incorecte."</string> <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați sistemul de infotainment sau ștergeți toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string> - <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați telefonul sau ștergeți toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string> + <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează telefonul sau șterge toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string> <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați tableta sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string> <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați dispozitivul Android TV sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string> <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați sistemul de infotainment sau ștergeți toate datele acestui profil dacă sunt introduse prea multe parole incorecte."</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați telefonul sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string> <string name="policylab_resetPassword" msgid="214556238645096520">"Să schimbe blocarea ecranului"</string> - <string name="policydesc_resetPassword" msgid="4626419138439341851">"Modificați blocarea ecranului."</string> + <string name="policydesc_resetPassword" msgid="4626419138439341851">"Modifică blocarea ecranului."</string> <string name="policylab_forceLock" msgid="7360335502968476434">"Să blocheze ecranul"</string> - <string name="policydesc_forceLock" msgid="1008844760853899693">"Stabiliți modul și timpul în care se blochează ecranul."</string> + <string name="policydesc_forceLock" msgid="1008844760853899693">"Stabilește cum și când se blochează ecranul."</string> <string name="policylab_wipeData" msgid="1359485247727537311">"Să șteargă toate datele"</string> <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Șterge datele de pe tabletă fără avertisment, efectuând resetarea configurării din fabrică."</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Șterge datele de pe dispozitivul Android TV fără avertisment, efectuând o revenire la setările din fabrică."</string> <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Șterge datele din sistemul de infotainment fără avertisment, prin revenirea la setările din fabrică."</string> - <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Șterge datele din telefon fără avertisment, efectuând resetarea configurării din fabrică."</string> + <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Șterge datele din telefon fără avertisment, revenind la setările din fabrică."</string> <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Șterge datele de profil"</string> <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Șterge datele utilizatorului"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Șterge datele acestui utilizator de pe această tabletă fără avertisment."</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Șterge datele acestui utilizator de pe acest dispozitiv Android TV fără avertisment"</string> <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Șterge datele profilului din acest sistem de infotainment fără avertisment."</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Șterge datele acestui utilizator de pe acest telefon fără avertisment."</string> - <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Setați serverul proxy global pentru dispozitiv"</string> - <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Setați serverul proxy global pentru dispozitiv, care să fie utilizat cât timp politica este activă. Numai proprietarul dispozitivului poate seta serverul proxy global."</string> - <string name="policylab_expirePassword" msgid="6015404400532459169">"Setați expirarea parolei pentru blocarea ecranului"</string> - <string name="policydesc_expirePassword" msgid="9136524319325960675">"Modificați frecvența cu care trebuie să se schimbe parola, codul PIN sau modelul pentru blocarea ecranului."</string> + <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Setează serverul proxy global pentru dispozitiv"</string> + <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Setează serverul proxy global pentru dispozitiv, care să fie utilizat cât timp politica este activă. Numai proprietarul dispozitivului poate seta serverul proxy global."</string> + <string name="policylab_expirePassword" msgid="6015404400532459169">"Setează expirarea parolei pentru blocarea ecranului"</string> + <string name="policydesc_expirePassword" msgid="9136524319325960675">"Modifică frecvența cu care trebuie să se schimbe parola, codul PIN sau modelul pentru blocarea ecranului."</string> <string name="policylab_encryptedStorage" msgid="9012936958126670110">"Să seteze criptarea stocării"</string> <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Necesită ca datele aplicației stocate să fie criptate."</string> <string name="policylab_disableCamera" msgid="5749486347810162018">"Să dezactiveze camerele foto"</string> - <string name="policydesc_disableCamera" msgid="3204405908799676104">"Împiedicați utilizarea camerelor foto de pe dispozitiv."</string> + <string name="policydesc_disableCamera" msgid="3204405908799676104">"Împiedică folosirea camerelor foto de pe dispozitiv."</string> <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"Să oprească funcții de blocare ecran"</string> - <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Împiedicați folosirea unor funcții de blocare a ecranului."</string> + <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Împiedică folosirea unor funcții de blocare a ecranului."</string> <string-array name="phoneTypes"> <item msgid="8996339953292723951">"Domiciliu"</item> <item msgid="7740243458912727194">"Mobil"</item> @@ -908,23 +908,23 @@ <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Introdu codul PUK și noul cod PIN"</string> <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"Codul PUK"</string> <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"Noul cod PIN"</string> - <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Atingeți ca să introduceți parola"</font></string> + <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Atinge ca să introduci parola"</font></string> <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Introdu parola pentru a debloca"</string> <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introdu codul PIN pentru a debloca"</string> <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Cod PIN incorect."</string> - <string name="keyguard_label_text" msgid="3841953694564168384">"Pentru a debloca, apăsați Meniu, apoi 0."</string> + <string name="keyguard_label_text" msgid="3841953694564168384">"Pentru a debloca, apasă Meniu, apoi 0."</string> <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Număr de urgență"</string> <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Fără semnal"</string> <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Ecranul este blocat."</string> <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Apasă Meniu pentru a debloca sau pentru a efectua apeluri de urgență."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Apasă Meniu pentru deblocare."</string> - <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenați modelul pentru a debloca"</string> + <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenează modelul pentru a debloca"</string> <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgență"</string> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Reveniți la apel"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Corect!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Încearcă din nou"</string> <string name="lockscreen_password_wrong" msgid="8605355913868947490">"Încearcă din nou"</string> - <string name="lockscreen_storage_locked" msgid="634993789186443380">"Deblocați pentru toate funcțiile și datele"</string> + <string name="lockscreen_storage_locked" msgid="634993789186443380">"Deblochează pentru toate funcțiile și datele"</string> <string name="faceunlock_multiple_failures" msgid="681991538434031708">"S-a depășit numărul maxim de încercări pentru Deblocare facială"</string> <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"Fără SIM"</string> <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"Nu există card SIM în computerul tablet PC."</string> @@ -937,10 +937,10 @@ <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Melodia anterioară"</string> <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Melodia următoare"</string> <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pauză"</string> - <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Redați"</string> + <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Redă"</string> <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Oprește"</string> - <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Derulați"</string> - <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Derulați rapid înainte"</string> + <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Derulează"</string> + <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Derulează rapid înainte"</string> <string name="emergency_calls_only" msgid="3057351206678279851">"Numai apeluri de urgență"</string> <string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Rețea blocată"</string> <string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"Cardul SIM este blocat cu codul PUK."</string> @@ -949,18 +949,18 @@ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"Se deblochează cardul SIM..."</string> <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> + <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Ai introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul datelor de conectare la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați dispozitivul Android TV prin conectarea la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul datelor de conectare la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va fi resetată la setările prestabilite din fabrică, iar toate datele de utilizator vor fi pierdute."</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va fi resetat la setările prestabilite din fabrică, iar toate datele de utilizator vor fi pierdute."</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi dispozitivul Android TV prin conectarea la Google.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi telefonul cu ajutorul datelor de conectare la Google.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string> <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va fi acum resetată la setările prestabilite din fabrică."</string> - <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string> + <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string> <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acesta va fi acum resetat la setările prestabilite din fabrică."</string> <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Încearcă din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string> - <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Ați uitat modelul?"</string> + <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Ai uitat modelul?"</string> <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Deblocare cont"</string> <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Prea multe încercări de desenare a modelului"</string> <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Pentru a debloca, conectați-vă folosind Contul Google."</string> @@ -970,7 +970,7 @@ <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Nume de utilizator sau parolă nevalide."</string> <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Ați uitat numele de utilizator sau parola?\nAccesați "<b>"google.com/accounts/recovery"</b>"."</string> <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Se verifică..."</string> - <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Deblocați"</string> + <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Deblochează"</string> <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sunet activat"</string> <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"Sunet dezactivat"</string> <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"Desenarea modelului a început"</string> @@ -1015,15 +1015,15 @@ <string name="factorytest_reboot" msgid="2050147445567257365">"Repornește"</string> <string name="js_dialog_title" msgid="7464775045615023241">"La pagina de la „<xliff:g id="TITLE">%s</xliff:g>” apare:"</string> <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string> - <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Confirmați părăsirea paginii"</string> - <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Părăsiți această pagină"</string> + <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Confirmă părăsirea paginii"</string> + <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Părăsește această pagină"</string> <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Rămâneți în această pagină"</string> - <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nSigur doriți să părăsiți această pagină?"</string> - <string name="save_password_label" msgid="9161712335355510035">"Confirmați"</string> - <string name="double_tap_toast" msgid="7065519579174882778">"Sfat: măriți și micșorați prin dublă atingere."</string> + <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nSigur părăsești această pagină?"</string> + <string name="save_password_label" msgid="9161712335355510035">"Confirmă"</string> + <string name="double_tap_toast" msgid="7065519579174882778">"Sfat: mărește și micșorează prin dublă atingere."</string> <string name="autofill_this_form" msgid="3187132440451621492">"Automat"</string> <string name="setup_autofill" msgid="5431369130866618567">"Conf.Compl.auto."</string> - <string name="autofill_window_title" msgid="4379134104008111961">"Completați automat cu <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string> + <string name="autofill_window_title" msgid="4379134104008111961">"Completează automat cu <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string> <string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string> <string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string> <string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string> @@ -1054,9 +1054,9 @@ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Permite aplicației să modifice permisiunile privind locația geografică a browserului. Aplicațiile rău intenționate pot utiliza această permisiune pentru a permite trimiterea informațiilor privind locația către site-uri web arbitrare."</string> <string name="save_password_message" msgid="2146409467245462965">"Doriți ca browserul să rețină această parolă?"</string> <string name="save_password_notnow" msgid="2878327088951240061">"Nu acum"</string> - <string name="save_password_remember" msgid="6490888932657708341">"Rețineți"</string> + <string name="save_password_remember" msgid="6490888932657708341">"Reține"</string> <string name="save_password_never" msgid="6776808375903410659">"Niciodată"</string> - <string name="open_permission_deny" msgid="5136793905306987251">"Nu aveți permisiunea de a deschide această pagină."</string> + <string name="open_permission_deny" msgid="5136793905306987251">"Nu ai permisiunea de a deschide această pagină."</string> <string name="text_copied" msgid="2531420577879738860">"Text copiat în clipboard."</string> <string name="pasted_from_app" msgid="5627698450808256545">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> a inserat date din <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string> <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> a inserat din clipboard"</string> @@ -1082,8 +1082,8 @@ <string name="searchview_description_submit" msgid="6771060386117334686">"Trimite interogarea"</string> <string name="searchview_description_voice" msgid="42360159504884679">"Căutare vocală"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Activați Explorați prin atingere?"</string> - <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorați prin atingere. Când această funcție este activată, puteți auzi sau vedea descrieri pentru ceea ce se află sub degetul dvs. sau puteți efectua gesturi pentru a interacționa cu tableta."</string> - <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorați prin atingere. Când această funcție este activată, puteți auzi sau vedea descrieri pentru ceea ce se află sub degetul dvs. sau puteți efectua gesturi pentru a interacționa cu telefonul."</string> + <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vrea să activeze funcția Explorează prin atingere. Când e activată, poți auzi sau vedea descrieri pentru ceea ce se află sub degetul tău sau poți face gesturi pentru a interacționa cu tableta."</string> + <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorează prin atingere. Când aceasta e activată, poți auzi sau vedea descrieri pentru ceea ce se află sub degetul tău sau poți face gesturi pentru a interacționa cu telefonul."</string> <string name="oneMonthDurationPast" msgid="4538030857114635777">"cu 1 lună în urmă"</string> <string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Cu mai mult de 1 lună în urmă"</string> <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Ultima zi}few{Ultimele # zile}other{Ultimele # de zile}}"</string> @@ -1143,7 +1143,7 @@ <string name="copyUrl" msgid="6229645005987260230">"Copiază adresa URL"</string> <string name="selectTextMode" msgid="3225108910999318778">"Selectează text"</string> <string name="undo" msgid="3175318090002654673">"Anulează"</string> - <string name="redo" msgid="7231448494008532233">"Repetați"</string> + <string name="redo" msgid="7231448494008532233">"Repetă"</string> <string name="autofill" msgid="511224882647795296">"Completare automată"</string> <string name="textSelectionCABTitle" msgid="5151441579532476940">"Selectare text"</string> <string name="addToDictionary" msgid="8041821113480950096">"Adaugă în dicționar"</string> @@ -1182,21 +1182,21 @@ <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Deschide linkurile cu <xliff:g id="APPLICATION">%1$s</xliff:g>"</string> <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Deschide linkurile <xliff:g id="HOST">%1$s</xliff:g> cu <xliff:g id="APPLICATION">%2$s</xliff:g>"</string> <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Permite accesul"</string> - <string name="whichEditApplication" msgid="6191568491456092812">"Editați cu"</string> - <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editați cu %1$s"</string> - <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editați"</string> + <string name="whichEditApplication" msgid="6191568491456092812">"Editează cu"</string> + <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editează cu %1$s"</string> + <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editează"</string> <string name="whichSendApplication" msgid="4143847974460792029">"Trimite"</string> - <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Distribuiți cu %1$s"</string> + <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Distribuie cu %1$s"</string> <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Trimite"</string> <string name="whichSendToApplication" msgid="77101541959464018">"Trimite folosind"</string> <string name="whichSendToApplicationNamed" msgid="3385686512014670003">"Trimite folosind %1$s"</string> <string name="whichSendToApplicationLabel" msgid="3543240188816513303">"Trimite"</string> <string name="whichHomeApplication" msgid="8276350727038396616">"Selectează o aplicație de pe ecranul de pornire"</string> <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"Utilizați %1$s ca ecran de pornire"</string> - <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Fotografiați"</string> - <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Fotografiați cu"</string> - <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Fotografiați cu %1$s"</string> - <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Fotografiați"</string> + <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Fotografiază"</string> + <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Fotografiază cu"</string> + <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Fotografiază cu %1$s"</string> + <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Fotografiază"</string> <string name="alwaysUse" msgid="3153558199076112903">"Se utilizează în mod prestabilit pentru această acțiune."</string> <string name="use_a_different_app" msgid="4987790276170972776">"Utilizați altă aplicație"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Șterge setările prestabilite din Setări de sistem > Aplicații > Descărcate."</string> @@ -1210,8 +1210,8 @@ <string name="aerr_restart" msgid="2789618625210505419">"Deschide din nou aplicația"</string> <string name="aerr_report" msgid="3095644466849299308">"Trimite feedback"</string> <string name="aerr_close" msgid="3398336821267021852">"Închide"</string> - <string name="aerr_mute" msgid="2304972923480211376">"Dezactivați până la repornirea dispozitivului"</string> - <string name="aerr_wait" msgid="3198677780474548217">"Așteptați"</string> + <string name="aerr_mute" msgid="2304972923480211376">"Dezactivează până la repornirea dispozitivului"</string> + <string name="aerr_wait" msgid="3198677780474548217">"Așteaptă"</string> <string name="aerr_close_app" msgid="8318883106083050970">"Închide aplicația"</string> <string name="anr_title" msgid="7290329487067300120"></string> <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> nu răspunde"</string> @@ -1220,14 +1220,14 @@ <string name="anr_process" msgid="1664277165911816067">"Procesul <xliff:g id="PROCESS">%1$s</xliff:g> nu răspunde"</string> <string name="force_close" msgid="9035203496368973803">"OK"</string> <string name="report" msgid="2149194372340349521">"Raportați"</string> - <string name="wait" msgid="7765985809494033348">"Așteptați"</string> - <string name="webpage_unresponsive" msgid="7850879412195273433">"Pagina a devenit inactivă.\n\nDoriți să o închideți?"</string> + <string name="wait" msgid="7765985809494033348">"Așteaptă"</string> + <string name="webpage_unresponsive" msgid="7850879412195273433">"Pagina a devenit inactivă.\n\nO închizi?"</string> <string name="launch_warning_title" msgid="6725456009564953595">"Aplicație redirecționată"</string> <string name="launch_warning_replace" msgid="3073392976283203402">"<xliff:g id="APP_NAME">%1$s</xliff:g> funcționează acum."</string> <string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost lansată inițial."</string> <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Scară"</string> <string name="screen_compat_mode_show" msgid="5080361367584709857">"Afișează întotdeauna"</string> - <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reactivați acest mod din Setări de sistem > Aplicații > Descărcate."</string> + <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reactivează acest mod din Setări de sistem > Aplicații > Descărcate."</string> <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu acceptă setarea actuală pentru Dimensiunea afișării și este posibil să aibă un comportament neașteptat."</string> <string name="unsupported_display_size_show" msgid="980129850974919375">"Afișează întotdeauna"</string> <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost concepută pentru o versiune incompatibilă de sistem de operare Android și este posibil să se comporte în mod neprevăzut. Poate fi disponibilă o versiune actualizată a aplicației."</string> @@ -1254,20 +1254,20 @@ <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Dezactivează"</string> <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuați cu verificarea amprentei?"</string> <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ați apăsat butonul de pornire. De obicei, această acțiune dezactivează ecranul.\n\nAtingeți ușor pentru verificarea amprentei."</string> - <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Dezactivați ecranul"</string> - <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuați"</string> + <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Dezactivează ecranul"</string> + <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuă"</string> <string name="heavy_weight_notification" msgid="8382784283600329576">"Rulează <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Atinge pentru a reveni la joc"</string> <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Alege jocul"</string> <string name="heavy_weight_switcher_text" msgid="6814316627367160126">"Pentru o performanță mai bună, se poate deschide un singur joc odată."</string> - <string name="old_app_action" msgid="725331621042848590">"Reveniți la <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_action" msgid="725331621042848590">"Revino la <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> <string name="new_app_action" msgid="547772182913269801">"Deschide <xliff:g id="NEW_APP">%1$s</xliff:g>"</string> <string name="new_app_description" msgid="1958903080400806644">"<xliff:g id="OLD_APP">%1$s</xliff:g> se va închide fără să salveze"</string> <string name="dump_heap_notification" msgid="5316644945404825032">"<xliff:g id="PROC">%1$s</xliff:g> a depășit limita de memorie"</string> <string name="dump_heap_ready_notification" msgid="2302452262927390268">"Datele privind memoria heap <xliff:g id="PROC">%1$s</xliff:g> sunt gata"</string> <string name="dump_heap_notification_detail" msgid="8431586843001054050">"Datele privind memoria au fost culese. Atinge pentru a trimite."</string> <string name="dump_heap_title" msgid="4367128917229233901">"Trimiți datele privind memoria?"</string> - <string name="dump_heap_text" msgid="1692649033835719336">"Procesul <xliff:g id="PROC">%1$s</xliff:g> și-a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le puteți trimite dezvoltatorului. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal la care aplicația are acces."</string> + <string name="dump_heap_text" msgid="1692649033835719336">"Procesul <xliff:g id="PROC">%1$s</xliff:g> și-a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le poți trimite dezvoltatorului. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal la care aplicația are acces."</string> <string name="dump_heap_system_text" msgid="6805155514925350849">"Procesul <xliff:g id="PROC">%1$s</xliff:g> a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le puteți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastați."</string> <string name="dump_heap_ready_text" msgid="5849618132123045516">"Sunt disponibile datele privind memoria heap a procesului <xliff:g id="PROC">%1$s</xliff:g>, pe care le puteți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastați."</string> <string name="sendText" msgid="493003724401350724">"Alege o acțiune pentru text"</string> @@ -1292,8 +1292,8 @@ <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Sunete de alarmă"</string> <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Sunete pentru notificare"</string> <string name="ringtone_unknown" msgid="5059495249862816475">"Necunoscut"</string> - <string name="wifi_available_sign_in" msgid="381054692557675237">"Conectați-vă la rețeaua Wi-Fi"</string> - <string name="network_available_sign_in" msgid="1520342291829283114">"Conectați-vă la rețea"</string> + <string name="wifi_available_sign_in" msgid="381054692557675237">"Conectează-te la rețeaua Wi-Fi"</string> + <string name="network_available_sign_in" msgid="1520342291829283114">"Conectează-te la rețea"</string> <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) --> <skip /> <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string> @@ -1322,29 +1322,29 @@ <string name="sms_control_yes" msgid="4858845109269524622">"Permite"</string> <string name="sms_control_no" msgid="4845717880040355570">"Refuz"</string> <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> intenționează să trimită un mesaj la <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>."</string> - <string name="sms_short_code_details" msgid="2723725738333388351">"Acest lucru "<b>"poate genera costuri"</b>" în contul dvs. mobil."</string> - <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Acest lucru va genera costuri în contul dvs. mobil."</b></string> + <string name="sms_short_code_details" msgid="2723725738333388351">"Acest lucru "<b>"poate genera costuri"</b>" în contul tău mobil."</string> + <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Acest lucru va genera costuri în contul tău mobil."</b></string> <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Trimite"</string> <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Anulează"</string> <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Doresc să se rețină opțiunea"</string> - <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Puteți modifica ulterior în Setări > Aplicații"</string> + <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Poți modifica ulterior în Setări > Aplicații"</string> <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Permite întotdeauna"</string> - <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nu permiteți niciodată"</string> + <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nu permite niciodată"</string> <string name="sim_removed_title" msgid="5387212933992546283">"Card SIM eliminat"</string> <string name="sim_removed_message" msgid="9051174064474904617">"Rețeaua mobilă va fi indisponibilă până când reporniți cu un card SIM valid introdus."</string> <string name="sim_done_button" msgid="6464250841528410598">"Terminat"</string> <string name="sim_added_title" msgid="7930779986759414595">"Card SIM adăugat"</string> <string name="sim_added_message" msgid="6602906609509958680">"Repornește dispozitivul pentru a accesa rețeaua mobilă."</string> <string name="sim_restart_button" msgid="8481803851341190038">"Repornește"</string> - <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activați serviciul mobil"</string> + <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activează serviciul mobil"</string> <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Descărcați aplicația operatorului pentru a vă activa noul card SIM"</string> - <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Descărcați aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> pentru a vă activa noul card SIM"</string> - <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Descărcați aplicația"</string> + <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Descarcă aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> pentru a-ți activa noul card SIM"</string> + <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Descarcă aplicația"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"S-a introdus un card SIM nou"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Atinge pentru a-l configura"</string> - <string name="time_picker_dialog_title" msgid="9053376764985220821">"Setați ora"</string> - <string name="date_picker_dialog_title" msgid="5030520449243071926">"Setați data"</string> - <string name="date_time_set" msgid="4603445265164486816">"Setați"</string> + <string name="time_picker_dialog_title" msgid="9053376764985220821">"Setează ora"</string> + <string name="date_picker_dialog_title" msgid="5030520449243071926">"Setează data"</string> + <string name="date_time_set" msgid="4603445265164486816">"Setează"</string> <string name="date_time_done" msgid="8363155889402873463">"Terminat"</string> <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NOU: "</font></string> <string name="perms_description_app" msgid="2747752389870161996">"Furnizată de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> @@ -1369,7 +1369,7 @@ <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Atinge pentru a dezactiva remedierea erorilor wireless"</string> <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Selectează pentru a dezactiva remedierea erorilor wireless."</string> <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Modul Set de testare este activat"</string> - <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Reveniți la setările din fabrică pentru a dezactiva modul Set de testare."</string> + <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Revino la setările din fabrică pentru a dezactiva modul Set de testare."</string> <string name="console_running_notification_title" msgid="6087888939261635904">"Consola din serie este activată"</string> <string name="console_running_notification_message" msgid="7892751888125174039">"Performanța este afectată. Pentru a dezactiva, verificați programul bootloader."</string> <string name="mte_override_notification_title" msgid="4731115381962792944">"MTE experimentală activată"</string> @@ -1395,7 +1395,7 @@ <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> se afișează peste alte aplicații"</string> <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> se afișează peste aplicații"</string> <string name="alert_windows_notification_message" msgid="6538171456970725333">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string> - <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Dezactivați"</string> + <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Dezactivează"</string> <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Se verifică <xliff:g id="NAME">%s</xliff:g>…"</string> <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Se examinează conținutul curent"</string> <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Se analizează spațiul de stocare media"</string> @@ -1425,15 +1425,15 @@ <string name="ext_media_init_action" msgid="2312974060585056709">"Configurează"</string> <string name="ext_media_unmount_action" msgid="966992232088442745">"Scoate"</string> <string name="ext_media_browse_action" msgid="344865351947079139">"Explorați"</string> - <string name="ext_media_seamless_action" msgid="8837030226009268080">"Schimbați ieșirea"</string> + <string name="ext_media_seamless_action" msgid="8837030226009268080">"Schimbă ieșirea"</string> <string name="ext_media_missing_title" msgid="3209472091220515046">"<xliff:g id="NAME">%s</xliff:g> lipsește"</string> - <string name="ext_media_missing_message" msgid="4408988706227922909">"Reintroduceți dispozitivul"</string> + <string name="ext_media_missing_message" msgid="4408988706227922909">"Reintrodu dispozitivul"</string> <string name="ext_media_move_specific_title" msgid="8492118544775964250">"Se mută <xliff:g id="NAME">%s</xliff:g>"</string> <string name="ext_media_move_title" msgid="2682741525619033637">"Se mută datele"</string> <string name="ext_media_move_success_title" msgid="4901763082647316767">"Transfer de conținut încheiat"</string> <string name="ext_media_move_success_message" msgid="9159542002276982979">"Conținut mutat pe <xliff:g id="NAME">%s</xliff:g>"</string> <string name="ext_media_move_failure_title" msgid="3184577479181333665">"Nu s-a putut muta conținutul"</string> - <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Încercați să mutați din nou conținutul"</string> + <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Încearcă să muți din nou conținutul"</string> <string name="ext_media_status_removed" msgid="241223931135751691">"Eliminat"</string> <string name="ext_media_status_unmounted" msgid="8145812017295835941">"Scos"</string> <string name="ext_media_status_checking" msgid="159013362442090347">"Se verifică..."</string> @@ -1460,7 +1460,7 @@ <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite unei aplicații să vadă toate pachetele instalate."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Apasă de două ori pentru a controla mărirea/micșorarea"</string> <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nu s-a putut adăuga widgetul."</string> - <string name="ime_action_go" msgid="5536744546326495436">"Accesați"</string> + <string name="ime_action_go" msgid="5536744546326495436">"Accesează"</string> <string name="ime_action_search" msgid="4501435960587287668">"Caută"</string> <string name="ime_action_send" msgid="8456843745664334138">"Trimite"</string> <string name="ime_action_next" msgid="4169702997635728543">"Înainte"</string> @@ -1477,13 +1477,13 @@ <string name="permission_request_notification_title" msgid="1810025922441048273">"Permisiune solicitată"</string> <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Permisiune solicitată\npentru contul <xliff:g id="ACCOUNT">%s</xliff:g>."</string> <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Permisiune solicitată de <xliff:g id="APP">%1$s</xliff:g>\npentru contul <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string> - <string name="forward_intent_to_owner" msgid="4620359037192871015">"Utilizați această aplicație în afara profilului de serviciu"</string> - <string name="forward_intent_to_work" msgid="3620262405636021151">"Utilizați această aplicație în profilul de serviciu"</string> + <string name="forward_intent_to_owner" msgid="4620359037192871015">"Folosești această aplicație în afara profilului de serviciu"</string> + <string name="forward_intent_to_work" msgid="3620262405636021151">"Folosești această aplicație în profilul de serviciu"</string> <string name="input_method_binding_label" msgid="1166731601721983656">"Metodă de intrare"</string> <string name="sync_binding_label" msgid="469249309424662147">"Sincronizare"</string> <string name="accessibility_binding_label" msgid="1974602776545801715">"Accesibilitate"</string> <string name="wallpaper_binding_label" msgid="1197440498000786738">"Imagine de fundal"</string> - <string name="chooser_wallpaper" msgid="3082405680079923708">"Schimbați imaginea de fundal"</string> + <string name="chooser_wallpaper" msgid="3082405680079923708">"Schimbă imaginea de fundal"</string> <string name="notification_listener_binding_label" msgid="2702165274471499713">"Serviciu de citire a notificărilor"</string> <string name="vr_listener_binding_label" msgid="8013112996671206429">"Instrument de ascultare pentru RV"</string> <string name="condition_provider_service_binding_label" msgid="8490641013951857673">"Furnizor de condiții"</string> @@ -1496,13 +1496,13 @@ <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Conectat(ă) la rețeaua VPN activată permanent"</string> <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Deconectat de la rețeaua VPN activată permanent"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Nu s-a putut conecta la rețeaua VPN activată permanent"</string> - <string name="vpn_lockdown_config" msgid="8331697329868252169">"Modificați setările de rețea sau VPN"</string> + <string name="vpn_lockdown_config" msgid="8331697329868252169">"Modifică setările de rețea sau VPN"</string> <string name="upload_file" msgid="8651942222301634271">"Alege un fișier"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Nu au fost găsite fișiere"</string> <string name="reset" msgid="3865826612628171429">"Resetează"</string> <string name="submit" msgid="862795280643405865">"Trimite"</string> <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Aplicația pentru condus rulează"</string> - <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Atingeți ca să ieșiți din aplicația pentru condus."</string> + <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Atinge ca să ieși din aplicația pentru condus."</string> <string name="back_button_label" msgid="4078224038025043387">"Înapoi"</string> <string name="next_button_label" msgid="6040209156399907780">"Înainte"</string> <string name="skip_button_label" msgid="3566599811326688389">"Omite"</string> @@ -1511,7 +1511,7 @@ <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# potrivire}few{# din {total}}other{# din {total}}}"</string> <string name="action_mode_done" msgid="2536182504764803222">"Terminat"</string> <string name="progress_erasing" msgid="6891435992721028004">"Se șterge spațiul de stocare distribuit..."</string> - <string name="share" msgid="4157615043345227321">"Distribuiți"</string> + <string name="share" msgid="4157615043345227321">"Distribuie"</string> <string name="find" msgid="5015737188624767706">"Găsiți"</string> <string name="websearch" msgid="5624340204512793290">"Căutare pe web"</string> <string name="find_next" msgid="5341217051549648153">"Următorul rezultat"</string> @@ -1532,16 +1532,16 @@ <string name="number_picker_increment_button" msgid="7621013714795186298">"Creșteți"</string> <string name="number_picker_decrement_button" msgid="5116948444762708204">"Reduceți"</string> <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> atingeți lung."</string> - <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Glisați în sus pentru a crește și în jos pentru a reduce."</string> + <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Glisează în sus pentru a crește și în jos pentru a reduce."</string> <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Creșteți valoarea pentru minute"</string> - <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Reduceți valoarea pentru minute"</string> + <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Redu valoarea pentru minute"</string> <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Creșteți valoarea pentru oră"</string> - <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Reduceți valoarea pentru oră"</string> - <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Setați valoarea PM"</string> - <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Setați valoarea AM"</string> - <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Creșteți valoarea pentru lună"</string> + <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Redu valoarea pentru oră"</string> + <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Setează valoarea PM"</string> + <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Setează valoarea AM"</string> + <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Mărește valoarea pentru lună"</string> <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Reduceți valoarea pentru lună"</string> - <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Creșteți valoarea pentru zi"</string> + <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Mărește valoarea pentru zi"</string> <string name="date_picker_decrement_day_button" msgid="6840253837656637248">"Reduceți valoarea pentru zi"</string> <string name="date_picker_increment_year_button" msgid="7608128783435372594">"Creșteți valoarea pentru an"</string> <string name="date_picker_decrement_year_button" msgid="4102586521754172684">"Reduceți valoarea pentru an"</string> @@ -1559,9 +1559,9 @@ <string name="shareactionprovider_share_with" msgid="2753089758467748982">"Permite accesul pentru"</string> <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Permite accesul pentru <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string> <string name="content_description_sliding_handle" msgid="982510275422590757">"Mâner glisant. Atingeți și țineți apăsat."</string> - <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Glisați pentru a debloca."</string> - <string name="action_bar_home_description" msgid="1501655419158631974">"Navigați la ecranul de pornire"</string> - <string name="action_bar_up_description" msgid="6611579697195026932">"Navigați în sus"</string> + <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Glisează pentru a debloca."</string> + <string name="action_bar_home_description" msgid="1501655419158631974">"Navighează la ecranul de pornire"</string> + <string name="action_bar_up_description" msgid="6611579697195026932">"Navighează în sus"</string> <string name="action_menu_overflow_description" msgid="4579536843510088170">"Mai multe opțiuni"</string> <string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s, %2$s"</string> <string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s, %2$s, %3$s"</string> @@ -1571,19 +1571,19 @@ <string name="storage_usb_drive" msgid="448030813201444573">"Unitate USB"</string> <string name="storage_usb_drive_label" msgid="6631740655876540521">"Unitate USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string> <string name="storage_usb" msgid="2391213347883616886">"Dsipozitiv de stocare USB"</string> - <string name="extract_edit_menu_button" msgid="63954536535863040">"Editați"</string> + <string name="extract_edit_menu_button" msgid="63954536535863040">"Editează"</string> <string name="data_usage_warning_title" msgid="9034893717078325845">"Avertisment pentru date"</string> - <string name="data_usage_warning_body" msgid="1669325367188029454">"Ați folosit <xliff:g id="APP">%s</xliff:g> din date"</string> + <string name="data_usage_warning_body" msgid="1669325367188029454">"Ai folosit <xliff:g id="APP">%s</xliff:g> din date"</string> <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"S-a atins limita de date mobile"</string> - <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Ați atins limita de date Wi-Fi"</string> + <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Ai atins limita de date Wi-Fi"</string> <string name="data_usage_limit_body" msgid="3567699582000085710">"Datele au fost întrerupte pentru restul ciclului"</string> <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Peste limita de date mobile"</string> <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Peste limita de date Wi-Fi"</string> - <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Ați depășit limita stabilită cu <xliff:g id="SIZE">%s</xliff:g>"</string> + <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Ai depășit limita stabilită cu <xliff:g id="SIZE">%s</xliff:g>"</string> <string name="data_usage_restricted_title" msgid="126711424380051268">"Datele de fundal restricționate"</string> - <string name="data_usage_restricted_body" msgid="5338694433686077733">"Atingeți ca să eliminați restricția."</string> + <string name="data_usage_restricted_body" msgid="5338694433686077733">"Atinge ca să elimini restricția."</string> <string name="data_usage_rapid_title" msgid="2950192123248740375">"Utilizare mare de date mobile"</string> - <string name="data_usage_rapid_body" msgid="3886676853263693432">"Aplicațiile dvs. au utilizat mai multe date decât de obicei"</string> + <string name="data_usage_rapid_body" msgid="3886676853263693432">"Aplicațiile au folosit mai multe date decât de obicei"</string> <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"<xliff:g id="APP">%s</xliff:g> a utilizat mai multe date decât de obicei"</string> <string name="ssl_certificate" msgid="5690020361307261997">"Certificat de securitate"</string> <string name="ssl_certificate_is_valid" msgid="7293675884598527081">"Certificatul este valid."</string> @@ -1601,9 +1601,9 @@ <string name="sha1_fingerprint" msgid="2339915142825390774">"Amprentă SHA-1:"</string> <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Afișează-le pe toate"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Alege activitatea"</string> - <string name="share_action_provider_share_with" msgid="1904096863622941880">"Distribuiți pentru"</string> + <string name="share_action_provider_share_with" msgid="1904096863622941880">"Distribuie pentru"</string> <string name="sending" msgid="206925243621664438">"Se trimite..."</string> - <string name="launchBrowserDefault" msgid="6328349989932924119">"Lansați browserul?"</string> + <string name="launchBrowserDefault" msgid="6328349989932924119">"Lansezi browserul?"</string> <string name="SetupCallDefault" msgid="5581740063237175247">"Accepți apelul?"</string> <string name="activity_resolver_use_always" msgid="5575222334666843269">"Întotdeauna"</string> <string name="activity_resolver_use_once" msgid="948462794469672658">"Numai o dată"</string> @@ -1619,8 +1619,8 @@ <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Audio Bluetooth"</string> <string name="wireless_display_route_description" msgid="8297563323032966831">"Ecran wireless"</string> <string name="media_route_button_content_description" msgid="2299223698196869956">"Trimite"</string> - <string name="media_route_chooser_title" msgid="6646594924991269208">"Conectați-vă la dispozitiv"</string> - <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Proiectați ecranul pe dispozitiv"</string> + <string name="media_route_chooser_title" msgid="6646594924991269208">"Conectează-te la dispozitiv"</string> + <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Proiectează ecranul pe dispozitiv"</string> <string name="media_route_chooser_searching" msgid="6119673534251329535">"Se caută dispozitive..."</string> <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Setări"</string> <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Deconectează-te"</string> @@ -1638,13 +1638,13 @@ <string name="kg_wrong_pattern" msgid="1342812634464179931">"Model greșit"</string> <string name="kg_wrong_password" msgid="2384677900494439426">"Parolă greșită"</string> <string name="kg_wrong_pin" msgid="3680925703673166482">"Cod PIN greșit"</string> - <string name="kg_pattern_instructions" msgid="8366024510502517748">"Desenați modelul"</string> + <string name="kg_pattern_instructions" msgid="8366024510502517748">"Desenează modelul"</string> <string name="kg_sim_pin_instructions" msgid="6479401489471690359">"Introdu codul PIN al cardului SIM"</string> <string name="kg_pin_instructions" msgid="7355933174673539021">"Introdu codul PIN"</string> <string name="kg_password_instructions" msgid="7179782578809398050">"Introdu parola"</string> <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"Cardul SIM este acum dezactivat. Introduceți codul PUK pentru a continua. Contactați operatorul pentru mai multe detalii."</string> <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Introdu codul PIN dorit"</string> - <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirmați codul PIN dorit"</string> + <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirmă codul PIN dorit"</string> <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"Se deblochează cardul SIM..."</string> <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Cod PIN incorect."</string> <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Introdu un cod PIN format din 4 până la 8 cifre."</string> @@ -1660,28 +1660,28 @@ <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"Ați uitat numele de utilizator sau parola?\nAccesați "<b>"google.com/accounts/recovery"</b>"."</string> <string name="kg_login_checking_password" msgid="4676010303243317253">"Se verifică contul…"</string> <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> + <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Ai introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va fi resetată la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va reveni la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string> <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va fi resetat la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string> - <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va fi acum resetată la setările prestabilite din fabrică."</string> - <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string> + <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Ați făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va reveni acum la setările din fabrică."</string> + <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Telefonul va fi acum resetat la setările prestabilite din fabrică."</string> <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați dispozitivul Android TV cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi telefonul cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> - <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eliminați"</string> + <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Elimină"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Atunci când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de trei secunde, veți lansa o funcție de accesibilitate."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activați comanda rapidă pentru funcțiile de accesibilitate?"</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activezi comanda rapidă pentru funcțiile de accesibilitate?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apăsați ambele taste de volum câteva secunde, activați funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuteți schimba funcțiile selectate din Setări > Accesibilitate."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activați comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activezi comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apăsați ambele taste de volum câteva secunde, activați funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPuteți alege altă funcție pentru această comandă în Setări > Accesibilitate."</string> - <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activați"</string> + <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activează"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nu activați"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"ACTIVAT"</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"DEZACTIVAT"</string> @@ -1697,10 +1697,10 @@ <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Alegeți funcțiile pe care să le folosiți cu butonul de accesibilitate"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Alege funcțiile pentru comanda rapidă a butonului de volum"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> a fost dezactivat"</string> - <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editați comenzile rapide"</string> + <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editează comenzile rapide"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Gata"</string> - <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Dezactivați comanda rapidă"</string> - <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizați comanda rapidă"</string> + <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Dezactivează comanda rapidă"</string> + <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Folosește comanda rapidă"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversarea culorilor"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Corecția culorii"</string> <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modul cu o mână"</string> @@ -1721,7 +1721,7 @@ <string name="owner_name" msgid="8713560351570795743">"Proprietar"</string> <string name="guest_name" msgid="8502103277839834324">"Invitat"</string> <string name="error_message_title" msgid="4082495589294631966">"Eroare"</string> - <string name="error_message_change_not_allowed" msgid="843159705042381454">"Această modificare nu este permisă de administratorul dvs."</string> + <string name="error_message_change_not_allowed" msgid="843159705042381454">"Această modificare nu este permisă de administrator"</string> <string name="app_not_found" msgid="3429506115332341800">"Nicio aplicație pentru gestionarea acestei acțiuni"</string> <string name="revoke" msgid="5526857743819590458">"Revocați"</string> <string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string> @@ -1830,7 +1830,7 @@ <string name="restr_pin_incorrect" msgid="3861383632940852496">"Incorect"</string> <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Codul PIN actual"</string> <string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"Codul PIN nou"</string> - <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirmați noul cod PIN"</string> + <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirmă noul cod PIN"</string> <string name="restr_pin_create_pin" msgid="917067613896366033">"Creați un cod PIN pentru modificarea restricțiilor"</string> <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"Codurile PIN nu se potrivesc. Încercați din nou."</string> <string name="restr_pin_error_too_short" msgid="1547007808237941065">"Codul PIN este prea scurt. Trebuie să aibă cel puțin 4 cifre."</string> @@ -1852,15 +1852,15 @@ <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicită codul PIN înainte de a anula fixarea"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicită mai întâi modelul pentru deblocare"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicită parola înainte de a anula fixarea"</string> - <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalat de administratorul dvs."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administratorul dvs."</string> + <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalat de administrator"</string> + <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administrator"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administratorul dvs."</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string> <string name="battery_saver_description" msgid="8518809702138617167">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string> - <string name="data_saver_description" msgid="4995164271550590517">"Pentru a contribui la reducerea utilizării de date, Economizorul de date împiedică unele aplicații să trimită sau să primească date în fundal. O aplicație pe care o folosiți poate accesa datele, însă mai rar. Aceasta poate însemna, de exemplu, că imaginile se afișează numai după ce le atingeți."</string> - <string name="data_saver_enable_title" msgid="7080620065745260137">"Activați Economizorul de date?"</string> - <string name="data_saver_enable_button" msgid="4399405762586419726">"Activați"</string> + <string name="data_saver_description" msgid="4995164271550590517">"Pentru a contribui la reducerea utilizării de date, Economizorul de date împiedică unele aplicații să trimită sau să primească date în fundal. O aplicație pe care o folosești poate accesa datele, însă mai rar. Aceasta poate însemna, de exemplu, că imaginile se afișează numai după ce le atingi."</string> + <string name="data_saver_enable_title" msgid="7080620065745260137">"Activezi Economizorul de date?"</string> + <string name="data_saver_enable_button" msgid="4399405762586419726">"Activează"</string> <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Timp de un minut (până la {formattedTime})}few{Timp de # minute (până la {formattedTime})}other{Timp de # de minute (până la {formattedTime})}}"</string> <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Timp de un min. (până la {formattedTime})}few{Timp de # min. (până la {formattedTime})}other{Timp de # min. (până la {formattedTime})}}"</string> <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{Timp de o oră (până la {formattedTime})}few{Timp de # ore (până la {formattedTime})}other{Timp de # de ore (până la {formattedTime})}}"</string> @@ -1884,7 +1884,7 @@ <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Somn"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string> - <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactați producătorul."</string> + <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactează producătorul."</string> <string name="stk_cc_ussd_to_dial" msgid="3139884150741157610">"Solicitarea USSD a fost schimbată cu un apel obișnuit"</string> <string name="stk_cc_ussd_to_ss" msgid="4826846653052609738">"Solicitarea USSD a fost schimbată cu o solicitare SS"</string> <string name="stk_cc_ussd_to_ussd" msgid="8343001461299302472">"Schimbat cu o solicitare USSD nouă"</string> @@ -1899,7 +1899,7 @@ <string name="notification_verified_content_description" msgid="6401483602782359391">"Confirmat"</string> <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Extinde"</string> <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Restrânge"</string> - <string name="expand_action_accessibility" msgid="1947657036871746627">"extindeți/restrângeți"</string> + <string name="expand_action_accessibility" msgid="1947657036871746627">"extinde/restrânge"</string> <string name="usb_midi_peripheral_name" msgid="490523464968655741">"Port USB Android periferic"</string> <string name="usb_midi_peripheral_manufacturer_name" msgid="7557148557088787741">"Android"</string> <string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"Port USB periferic"</string> @@ -1908,7 +1908,7 @@ <string name="maximize_button_text" msgid="4258922519914732645">"Maximizați"</string> <string name="close_button_text" msgid="10603510034455258">"Închide"</string> <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string> - <string name="call_notification_answer_action" msgid="5999246836247132937">"Răspundeți"</string> + <string name="call_notification_answer_action" msgid="5999246836247132937">"Răspunde"</string> <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string> <string name="call_notification_decline_action" msgid="3700345945214000726">"Respingeți"</string> <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Încheiați"</string> @@ -1916,7 +1916,7 @@ <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Apel în desfășurare"</string> <string name="call_notification_screening_text" msgid="8396931408268940208">"Se filtrează un apel primit"</string> <string name="default_notification_channel_label" msgid="3697928973567217330">"Neclasificate"</string> - <string name="importance_from_user" msgid="2782756722448800447">"Dvs. setați importanța acestor notificări."</string> + <string name="importance_from_user" msgid="2782756722448800447">"Tu setezi importanța acestor notificări."</string> <string name="importance_from_person" msgid="4235804979664465383">"Notificarea este importantă având în vedere persoanele implicate."</string> <string name="notification_history_title_placeholder" msgid="7748630986182249599">"Notificare de aplicație personalizată"</string> <string name="user_creation_account_exists" msgid="2239146360099708035">"Permiți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>? (există deja un utilizator cu acest cont)"</string> @@ -1936,15 +1936,15 @@ <string name="app_suspended_default_message" msgid="6451215678552004172">"Momentan, aplicația <xliff:g id="APP_NAME_0">%1$s</xliff:g> nu este disponibilă. Aceasta este gestionată de <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string> <string name="app_suspended_more_details" msgid="211260942831587014">"Află mai multe"</string> <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Anulează întreruperea aplicației"</string> - <string name="work_mode_off_title" msgid="961171256005852058">"Activați aplicațiile pentru lucru?"</string> + <string name="work_mode_off_title" msgid="961171256005852058">"Activezi aplicațiile pentru lucru?"</string> <string name="work_mode_off_message" msgid="7319580997683623309">"Obțineți acces la aplicațiile pentru lucru și notificări"</string> - <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string> + <string name="work_mode_turn_on" msgid="3662561662475962285">"Activează"</string> <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string> <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string> <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nu este disponibilă"</string> <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Necesită permisiune"</string> <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera video nu este disponibilă"</string> - <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuați pe telefon"</string> + <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuă pe telefon"</string> <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfon indisponibil"</string> <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Aplicația Magazin Play nu este disponibilă"</string> <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Setările pentru Android TV sunt indisponibile"</string> @@ -1962,10 +1962,10 @@ <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Caută actualizări"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string> - <string name="new_sms_notification_content" msgid="3197949934153460639">"Deschideți aplicația pentru SMS-uri ca să vizualizați"</string> + <string name="new_sms_notification_content" msgid="3197949934153460639">"Deschide aplicația pentru SMS-uri ca să vezi"</string> <string name="profile_encrypted_title" msgid="9001208667521266472">"Unele funcții ar putea fi limitate"</string> <string name="profile_encrypted_detail" msgid="5279730442756849055">"Profil de serviciu blocat"</string> - <string name="profile_encrypted_message" msgid="1128512616293157802">"Atingeți ca să deblocați"</string> + <string name="profile_encrypted_message" msgid="1128512616293157802">"Atinge ca să deblochezi"</string> <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectat la <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string> <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Atinge pentru a vedea fișierele"</string> <string name="pin_target" msgid="8036028973110156895">"Fixați"</string> @@ -1992,11 +1992,11 @@ <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Remedierea erorilor prin USB"</string> <string name="time_picker_hour_label" msgid="4208590187662336864">"oră"</string> <string name="time_picker_minute_label" msgid="8307452311269824553">"minut"</string> - <string name="time_picker_header_text" msgid="9073802285051516688">"Setați ora"</string> + <string name="time_picker_header_text" msgid="9073802285051516688">"Setează ora"</string> <string name="time_picker_input_error" msgid="8386271930742451034">"Introdu o oră validă"</string> <string name="time_picker_prompt_label" msgid="303588544656363889">"Introdu ora"</string> <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Pentru a introduce ora, comutați la modul de introducere a textului."</string> - <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Pentru a introduce ora, comutați la modul ceas."</string> + <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Pentru a introduce ora, comută la modul ceas."</string> <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opțiuni de completare automată"</string> <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Salvează pentru completare automată"</string> <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Conținutul nu poate fi completat automat"</string> @@ -2015,7 +2015,7 @@ <string name="autofill_save_notnow" msgid="2853932672029024195">"Nu acum"</string> <string name="autofill_save_never" msgid="6821841919831402526">"Niciodată"</string> <string name="autofill_update_yes" msgid="4608662968996874445">"Actualizează"</string> - <string name="autofill_continue_yes" msgid="7914985605534510385">"Continuați"</string> + <string name="autofill_continue_yes" msgid="7914985605534510385">"Continuă"</string> <string name="autofill_save_type_password" msgid="5624528786144539944">"parolă"</string> <string name="autofill_save_type_address" msgid="3111006395818252885">"adresă"</string> <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"card de credit"</string> @@ -2028,7 +2028,7 @@ <string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"Părăsiți imediat zonele de coastă și din apropierea râurilor și îndreptați-vă spre un loc mai sigur, cum ar fi o zonă aflată la înălțime."</string> <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="4888224011071875068">"Păstrați-vă calmul și căutați un adăpost în apropiere."</string> <string name="etws_primary_default_message_test" msgid="4583367373909549421">"Testarea mesajelor de urgență"</string> - <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Răspundeți"</string> + <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Răspunde"</string> <string name="etws_primary_default_message_others" msgid="7958161706019130739"></string> <string name="mmcc_authentication_reject" msgid="4891965994643876369">"Cardul SIM nu este permis pentru voce"</string> <string name="mmcc_imsi_unknown_in_hlr" msgid="227760698553988751">"Cardul SIM nu este activat pentru voce"</string> @@ -2050,11 +2050,11 @@ <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicație dăunătoare detectată"</string> <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permiți ca <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> să acceseze toate jurnalele dispozitivului?"</string> <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permite accesul o dată"</string> - <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permiteți"</string> + <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permite"</string> <string name="log_access_confirmation_body" msgid="1806692062668620735">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string> <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nu mai afișa"</string> <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vrea să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>"</string> - <string name="screenshot_edit" msgid="7408934887203689207">"Editați"</string> + <string name="screenshot_edit" msgid="7408934887203689207">"Editează"</string> <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Apelurile și notificările vor vibra"</string> <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Apelurile și notificările vor avea sunetul dezactivat"</string> <string name="notification_channel_system_changes" msgid="2462010596920209678">"Modificări de sistem"</string> @@ -2080,7 +2080,7 @@ <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificări optimizate"</string> <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Acțiunile și răspunsurile sugerate sunt acum trimise prin notificări optimizate. Notificările adaptive Android nu mai sunt acceptate."</string> <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string> - <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Dezactivați"</string> + <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Dezactivează"</string> <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Află mai multe"</string> <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Notificările optimizate au înlocuit Notificările adaptive Android de pe Android 12. Această funcție afișează acțiuni și răspunsuri sugerate și vă organizează notificările.\n\nNotificările optimizate pot accesa conținutul notificărilor, inclusiv informații cu caracter personal, precum mesajele și numele persoanelor de contact. În plus, funcția poate să închidă sau să răspundă la notificări, de exemplu, să răspundă la apeluri telefonice și să gestioneze opțiunea Nu deranja."</string> <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificare pentru informații despre modul Rutină"</string> @@ -2153,10 +2153,10 @@ <string name="resolver_switch_on_work" msgid="463709043650610420">"Atinge pentru a activa"</string> <string name="resolver_no_work_apps_available" msgid="3298291360133337270">"Nicio aplicație pentru lucru"</string> <string name="resolver_no_personal_apps_available" msgid="6284837227019594881">"Nicio aplicație personală"</string> - <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Deschideți <xliff:g id="APP">%s</xliff:g> în profilul personal?"</string> - <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Deschideți <xliff:g id="APP">%s</xliff:g> în profilul de serviciu?"</string> - <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Folosiți browserul personal"</string> - <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Folosiți browserul de serviciu"</string> + <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Deschizi <xliff:g id="APP">%s</xliff:g> în profilul personal?"</string> + <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Deschizi <xliff:g id="APP">%s</xliff:g> în profilul de serviciu?"</string> + <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Folosește browserul personal"</string> + <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Folosește browserul de serviciu"</string> <string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY" msgid="8050953231914637819">"Codul PIN de deblocare SIM privind rețeaua"</string> <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ENTRY" msgid="7164399703751688214">"Codul PIN de deblocare SIM privind subsetul de rețea"</string> <string name="PERSOSUBSTATE_SIM_CORPORATE_ENTRY" msgid="4447629474818217364">"Codul PIN de deblocare SIM corporativă"</string> @@ -2271,12 +2271,12 @@ <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="6024904218067254186"></string> <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Noi setări de mărire"</string> <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Acum puteți mări o parte a ecranului"</string> - <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activați din Setări"</string> + <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activează din Setări"</string> <string name="dismiss_action" msgid="1728820550388704784">"Respingeți"</string> - <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Deblocați microfonul dispozitivului"</string> - <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Deblocați camera dispozitivului"</string> + <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Deblochează microfonul dispozitivului"</string> + <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Deblochează camera dispozitivului"</string> <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Pentru <b><xliff:g id="APP">%s</xliff:g></b> și toate aplicațiile și serviciile"</string> - <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Deblocați"</string> + <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Deblochează"</string> <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Confidențialitatea privind senzorii"</string> <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Pictograma aplicației"</string> <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Imaginea de branding a aplicației"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 71668fadc310..bad041a25264 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -263,7 +263,7 @@ <string name="global_action_settings" msgid="4671878836947494217">"Настройки"</string> <string name="global_action_assist" msgid="2517047220311505805">"Помощник"</string> <string name="global_action_voice_assist" msgid="6655788068555086695">"Аудиоподсказки"</string> - <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка"</string> + <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string> <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуальная клавиатура"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index c76d6a37e316..750c16cda777 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1080,7 +1080,7 @@ <string name="searchview_description_search" msgid="1045552007537359343">"Пошук"</string> <string name="searchview_description_query" msgid="7430242366971716338">"Пошуковий запит"</string> <string name="searchview_description_clear" msgid="1989371719192982900">"Очистити запит"</string> - <string name="searchview_description_submit" msgid="6771060386117334686">"Наіслати запит"</string> + <string name="searchview_description_submit" msgid="6771060386117334686">"Надіслати запит"</string> <string name="searchview_description_voice" msgid="42360159504884679">"Голосовий пошук"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Увімкнути дослідження дотиком?"</string> <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> хоче ввімкнути функцію дослідження дотиком. Увімкнувши функцію дослідження дотиком, можна чути або бачити опис елемента, розташованого під вашим пальцем, або виконувати жести для взаємодії з планшетним ПК."</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1d2ce7eb37c3..060f440c9c4f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1662,13 +1662,18 @@ darkening hysteresis constraint value is the n-th element of config_screenDarkeningThresholds. + Historically, it has been assumed that this will be an integer array with values in the + range of [0, 255]. However, it is now assumed to be a float array with values in the + range of [0, 1]. To accommodate both the possibilities, we internally check the scale on + which the thresholds are defined, and calibrate it accordingly. + The (zero-based) index is calculated as follows: (MAX is the largest index of the array) condition calculated index value < level[0] 0 level[n] <= value < level[n+1] n+1 level[MAX] <= value MAX+1 --> - <integer-array name="config_screenThresholdLevels"> - </integer-array> + <array name="config_screenThresholdLevels"> + </array> <!-- Array of hysteresis constraint values for brightening, represented as tenths of a percent. The length of this array is assumed to be one greater than diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 9214f438826f..9b060593651f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5760,10 +5760,21 @@ <string name="log_access_confirmation_deny">Don\u2019t allow</string> <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> - <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. + <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device. </string> + <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> + <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. + \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs. + </string> + + <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]--> + <string name="log_access_confirmation_learn_more" product="default" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string> + + <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]--> + <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string> + <!-- Privacy notice do not show [CHAR LIMIT=20] --> <string name="log_access_do_not_show_again">Don\u2019t show again</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 95a81e82a2f4..c3d40889636d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3934,8 +3934,10 @@ <java-symbol type="string" name="log_access_confirmation_deny" /> <java-symbol type="string" name="log_access_confirmation_title" /> <java-symbol type="string" name="log_access_confirmation_body" /> + <java-symbol type="string" name="log_access_confirmation_learn_more" /> <java-symbol type="layout" name="log_access_user_consent_dialog_permission" /> <java-symbol type="id" name="log_access_dialog_title" /> + <java-symbol type="id" name="log_access_dialog_body" /> <java-symbol type="id" name="log_access_dialog_allow_button" /> <java-symbol type="id" name="log_access_dialog_deny_button" /> diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 4c247427ef8f..9e39e13265bd 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -16,11 +16,12 @@ package android.hardware.devicestate; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -36,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mockito; import java.util.HashSet; import java.util.Set; @@ -59,6 +59,7 @@ public final class DeviceStateManagerGlobalTest { public void setUp() { mService = new TestDeviceStateManagerService(); mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); + assertFalse(mService.mCallbacks.isEmpty()); } @Test @@ -79,8 +80,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onBaseStateChanged(eq(mService.getBaseState())); verify(callback2).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the supported states and verify callback mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE }); @@ -88,8 +89,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates())); mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the base state and verify callback mService.setBaseState(OTHER_DEVICE_STATE); @@ -98,8 +99,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onBaseStateChanged(eq(mService.getBaseState())); verify(callback2).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the requested state and verify callback DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build(); @@ -120,7 +121,7 @@ public final class DeviceStateManagerGlobalTest { verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates())); verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback); + reset(callback); mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback); @@ -130,33 +131,86 @@ public final class DeviceStateManagerGlobalTest { } @Test - public void submittingRequestRegistersCallback() { - assertTrue(mService.mCallbacks.isEmpty()); + public void submitRequest() { + DeviceStateCallback callback = mock(DeviceStateCallback.class); + mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, + ConcurrentUtils.DIRECT_EXECUTOR); - DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build(); + verify(callback).onStateChanged(eq(mService.getBaseState())); + reset(callback); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); - assertFalse(mService.mCallbacks.isEmpty()); + verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); + reset(callback); + + mDeviceStateManagerGlobal.cancelStateRequest(); + + verify(callback).onStateChanged(eq(mService.getBaseState())); } @Test - public void submitRequest() { + public void submitBaseStateOverrideRequest() { DeviceStateCallback callback = mock(DeviceStateCallback.class); mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR); + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getBaseState())); - Mockito.reset(callback); + reset(callback); DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); - mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); + mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */, + null /* callback */); + verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE)); verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); - Mockito.reset(callback); + reset(callback); - mDeviceStateManagerGlobal.cancelStateRequest(); + mDeviceStateManagerGlobal.cancelBaseStateOverride(); + + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); + verify(callback).onStateChanged(eq(mService.getBaseState())); + } + + @Test + public void submitBaseAndEmulatedStateOverride() { + DeviceStateCallback callback = mock(DeviceStateCallback.class); + mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, + ConcurrentUtils.DIRECT_EXECUTOR); + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getBaseState())); + reset(callback); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); + mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */, + null /* callback */); + + verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE)); + verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); + assertEquals(OTHER_DEVICE_STATE, mService.getBaseState()); + reset(callback); + + DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder( + DEFAULT_DEVICE_STATE).build(); + + mDeviceStateManagerGlobal.requestState(secondRequest, null, null); + + assertEquals(OTHER_DEVICE_STATE, mService.getBaseState()); + verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE)); + reset(callback); + + mDeviceStateManagerGlobal.cancelStateRequest(); + + verify(callback).onStateChanged(OTHER_DEVICE_STATE); + reset(callback); + + mDeviceStateManagerGlobal.cancelBaseStateOverride(); + + verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE); + verify(callback).onStateChanged(DEFAULT_DEVICE_STATE); } @Test @@ -169,7 +223,7 @@ public final class DeviceStateManagerGlobalTest { callback /* callback */); verify(callback).onRequestActivated(eq(request)); - Mockito.reset(callback); + reset(callback); mDeviceStateManagerGlobal.cancelStateRequest(); @@ -203,13 +257,16 @@ public final class DeviceStateManagerGlobalTest { private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }; private int mBaseState = DEFAULT_DEVICE_STATE; private Request mRequest; + private Request mBaseStateRequest; private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); private DeviceStateInfo getInfo() { + final int mergedBaseState = mBaseStateRequest == null + ? mBaseState : mBaseStateRequest.state; final int mergedState = mRequest == null - ? mBaseState : mRequest.state; - return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState); + ? mergedBaseState : mRequest.state; + return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState); } private void notifyDeviceStateInfoChanged() { @@ -238,7 +295,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onDeviceStateInfoChanged(getInfo()); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } @@ -249,7 +306,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestCanceled(mRequest.token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } } @@ -262,7 +319,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestActive(token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } } @@ -275,7 +332,46 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestCanceled(token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); + } + } + notifyDeviceStateInfoChanged(); + } + + @Override + public void requestBaseStateOverride(IBinder token, int state, int flags) { + if (mBaseStateRequest != null) { + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestCanceled(mBaseStateRequest.token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + final Request request = new Request(token, state, flags); + mBaseStateRequest = request; + notifyDeviceStateInfoChanged(); + + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestActive(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + @Override + public void cancelBaseStateOverride() throws RemoteException { + IBinder token = mBaseStateRequest.token; + mBaseStateRequest = null; + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestCanceled(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } notifyDeviceStateInfoChanged(); @@ -296,7 +392,7 @@ public final class DeviceStateManagerGlobalTest { } public int getBaseState() { - return mBaseState; + return getInfo().baseState; } public int getMergedState() { diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index 940ca96fac4f..4679a9ea6f66 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -19,14 +19,15 @@ package com.android.internal.util; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -51,8 +52,11 @@ import com.android.internal.widget.IWeakEscrowTokenActivatedListener; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; +import com.google.android.collect.Lists; + import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.nio.charset.StandardCharsets; @@ -172,13 +176,61 @@ public class LockPatternUtilsTest { } @Test - public void testGetEnabledTrustAgentsNotNull() throws RemoteException { + public void testSetEnabledTrustAgents() throws RemoteException { int testUserId = 10; ILockSettings ils = createTestLockSettings(); - when(ils.getString(anyString(), any(), anyInt())).thenReturn(""); + ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); + doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); + List<ComponentName> enabledTrustAgents = Lists.newArrayList( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + + mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId); + + assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); + } + + @Test + public void testGetEnabledTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + when(ils.getString(anyString(), any(), anyInt())).thenReturn( + "com.android/.TrustAgent,com.test/.TestAgent"); + List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId); - assertNotNull(trustAgents); - assertEquals(0, trustAgents.size()); + + assertThat(trustAgents).containsExactly( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + } + + @Test + public void testSetKnownTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); + doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); + List<ComponentName> knownTrustAgents = Lists.newArrayList( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + + mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId); + + assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); + } + + @Test + public void testGetKnownTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + when(ils.getString(anyString(), any(), anyInt())).thenReturn( + "com.android/.TrustAgent,com.test/.TestAgent"); + + List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId); + + assertThat(trustAgents).containsExactly( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); } private ILockSettings createTestLockSettings() { diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 682483d9ce63..cbdef846ceaf 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -373,12 +373,6 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/TransitionController.java" }, - "-1750384749": { - "message": "Launch on display check: allow launch on public display", - "level": "DEBUG", - "group": "WM_DEBUG_TASKS", - "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java" - }, "-1750206390": { "message": "Exception thrown when creating surface for client %s (%s). %s", "level": "WARN", @@ -907,6 +901,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1253056469": { + "message": "Launch on display check: %s launch for userId=%d on displayId=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TASKS", + "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java" + }, "-1248645819": { "message": "\tAdd container=%s", "level": "DEBUG", @@ -3115,6 +3115,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, + "829869827": { + "message": "Cannot launch dream activity due to invalid state. dreaming: %b packageName: %s", + "level": "ERROR", + "group": "WM_DEBUG_DREAM", + "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" + }, "835814848": { "message": "%s", "level": "INFO", @@ -4141,6 +4147,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, + "1918771553": { + "message": "Dream packageName does not match active dream. Package %s does not match %s or %s", + "level": "ERROR", + "group": "WM_DEBUG_DREAM", + "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" + }, "1921821199": { "message": "Preserving %s until the new one is added", "level": "VERBOSE", @@ -4383,6 +4395,9 @@ "WM_DEBUG_DRAW": { "tag": "WindowManager" }, + "WM_DEBUG_DREAM": { + "tag": "WindowManager" + }, "WM_DEBUG_FOCUS": { "tag": "WindowManager" }, diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 25e1922fc38e..3b7d0e14893c 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -195,6 +195,8 @@ public class Typeface { @UnsupportedAppUsage public final long native_instance; + private final String mSystemFontFamilyName; + private final Runnable mCleaner; /** @hide */ @@ -272,6 +274,14 @@ public class Typeface { } /** + * Returns the system font family name if the typeface was created from a system font family, + * otherwise returns null. + */ + public final @Nullable String getSystemFontFamilyName() { + return mSystemFontFamilyName; + } + + /** * Returns true if the system has the font family with the name [familyName]. For example * querying with "sans-serif" would check if the "sans-serif" family is defined in the system * and return true if does. @@ -870,7 +880,7 @@ public class Typeface { final int italic = (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; return new Typeface(nativeCreateFromArray( - ptrArray, fallbackTypeface.native_instance, weight, italic)); + ptrArray, fallbackTypeface.native_instance, weight, italic), null); } } @@ -935,7 +945,8 @@ public class Typeface { } } - typeface = new Typeface(nativeCreateFromTypeface(ni, style)); + typeface = new Typeface(nativeCreateFromTypeface(ni, style), + family.getSystemFontFamilyName()); styles.put(style, typeface); } return typeface; @@ -1003,7 +1014,8 @@ public class Typeface { } typeface = new Typeface( - nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic)); + nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic), + base.getSystemFontFamilyName()); innerCache.put(key, typeface); } return typeface; @@ -1013,7 +1025,10 @@ public class Typeface { public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { final Typeface base = family == null ? Typeface.DEFAULT : family; - return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes)); + Typeface typeface = new Typeface( + nativeCreateFromTypefaceWithVariation(base.native_instance, axes), + base.getSystemFontFamilyName()); + return typeface; } /** @@ -1108,7 +1123,7 @@ public class Typeface { } return new Typeface(nativeCreateFromArray( ptrArray, 0, RESOLVE_BY_FONT_TABLE, - RESOLVE_BY_FONT_TABLE)); + RESOLVE_BY_FONT_TABLE), null); } /** @@ -1116,13 +1131,14 @@ public class Typeface { * * @param families array of font families */ - private static Typeface createFromFamilies(@Nullable FontFamily[] families) { + private static Typeface createFromFamilies(@NonNull String familyName, + @Nullable FontFamily[] families) { final long[] ptrArray = new long[families.length]; for (int i = 0; i < families.length; ++i) { ptrArray[i] = families[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, 0, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), familyName); } /** @@ -1162,12 +1178,17 @@ public class Typeface { ptrArray[i] = families[i].mNativePtr; } return new Typeface(nativeCreateFromArray( - ptrArray, fallbackTypeface.native_instance, weight, italic)); + ptrArray, fallbackTypeface.native_instance, weight, italic), null); } // don't allow clients to call this directly @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Typeface(long ni) { + this(ni, null); + } + + // don't allow clients to call this directly + private Typeface(long ni, @Nullable String systemFontFamilyName) { if (ni == 0) { throw new RuntimeException("native typeface cannot be made"); } @@ -1176,6 +1197,7 @@ public class Typeface { mCleaner = sRegistry.registerNativeAllocation(this, native_instance); mStyle = nativeGetStyle(ni); mWeight = nativeGetWeight(ni); + mSystemFontFamilyName = systemFontFamilyName; } /** @@ -1200,7 +1222,8 @@ public class Typeface { List<FontConfig.Alias> aliases, Map<String, Typeface> outSystemFontMap) { for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) { - outSystemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); + outSystemFontMap.put(entry.getKey(), + createFromFamilies(entry.getKey(), entry.getValue())); } for (int i = 0; i < aliases.size(); ++i) { @@ -1215,8 +1238,8 @@ public class Typeface { continue; } final int weight = alias.getWeight(); - final Typeface newFace = weight == 400 ? base : - new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); + final Typeface newFace = weight == 400 ? base : new Typeface( + nativeCreateWeightAlias(base.native_instance, weight), alias.getName()); outSystemFontMap.put(alias.getName(), newFace); } } @@ -1288,7 +1311,7 @@ public class Typeface { buffer.position(buffer.position() + typefacesBytesCount); for (long nativePtr : nativePtrs) { String name = readString(buffer); - out.put(name, new Typeface(nativePtr)); + out.put(name, new Typeface(nativePtr, name)); } return nativePtrs; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index 7e9c4189dabb..fb0a9db6a20b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -41,7 +41,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 2; + return 1; } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index febd7917dff9..74303e2fab7c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -31,6 +31,7 @@ import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -93,6 +94,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } /** No longer overrides the animation if the transition is on the given Task. */ + @GuardedBy("mLock") void stopOverrideSplitAnimation(int taskId) { if (mAnimationController != null) { mAnimationController.unregisterRemoteAnimations(taskId); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index c8ac0fc73ff9..00be5a6e3416 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -17,8 +17,10 @@ package androidx.window.extensions.embedding; import android.app.Activity; +import android.content.res.Configuration; import android.util.Pair; import android.util.Size; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -32,14 +34,18 @@ class SplitContainer { private final TaskFragmentContainer mSecondaryContainer; @NonNull private final SplitRule mSplitRule; + @NonNull + private SplitAttributes mSplitAttributes; SplitContainer(@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, - @NonNull SplitRule splitRule) { + @NonNull SplitRule splitRule, + @NonNull SplitAttributes splitAttributes) { mPrimaryContainer = primaryContainer; mSecondaryContainer = secondaryContainer; mSplitRule = splitRule; + mSplitAttributes = splitAttributes; if (shouldFinishPrimaryWithSecondary(splitRule)) { if (mPrimaryContainer.getRunningActivityCount() == 1 @@ -72,6 +78,26 @@ class SplitContainer { return mSplitRule; } + @NonNull + SplitAttributes getSplitAttributes() { + return mSplitAttributes; + } + + /** + * Updates the {@link SplitAttributes} to this container. + * It is usually used when there's a folding state change or + * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int, + * Configuration)}. + */ + void setSplitAttributes(@NonNull SplitAttributes splitAttributes) { + mSplitAttributes = splitAttributes; + } + + @NonNull + TaskContainer getTaskContainer() { + return getPrimaryContainer().getTaskContainer(); + } + /** Returns the minimum dimension pair of primary container and secondary container. */ @NonNull Pair<Size, Size> getMinDimensionsPair() { @@ -141,6 +167,7 @@ class SplitContainer { + " primaryContainer=" + mPrimaryContainer + " secondaryContainer=" + mSecondaryContainer + " splitRule=" + mSplitRule + + " splitAttributes" + mSplitAttributes + "}"; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 126f8350839c..203ece091e46 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; @@ -40,12 +41,13 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds; -import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; import android.app.ActivityThread; +import android.app.Application; import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; @@ -62,19 +64,25 @@ import android.util.Log; import android.util.Pair; import android.util.Size; import android.util.SparseArray; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.common.CommonFoldingFeature; import androidx.window.common.EmptyLifecycleCallbacksAdapter; +import androidx.window.extensions.WindowExtensionsProvider; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -96,6 +104,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** + * A developer-defined {@link SplitAttributes} calculator to compute the current + * {@link SplitAttributes} with the current device and window states. + * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)} + * and unregistered via {@link #clearSplitAttributesCalculator()}. + * This is called when: + * <ul> + * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer, + * WindowContainerTransaction)}</li> + * <li>There's a started Activity which matches {@link SplitPairRule} </li> + * <li>Checking whether the place holder should be launched if there's a Activity matches + * {@link SplitPlaceholderRule} </li> + * </ul> + */ + @GuardedBy("mLock") + @Nullable + private SplitAttributesCalculator mSplitAttributesCalculator; + /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * below it. * When the app is host of multiple Tasks, there can be multiple splits controlled by the same @@ -105,26 +130,65 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); - // Callback to Jetpack to notify about changes to split states. - @NonNull + /** Callback to Jetpack to notify about changes to split states. */ + @Nullable private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; + @NonNull + final WindowLayoutComponentImpl mWindowLayoutComponent; public SplitController() { + this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider + .getWindowExtensions().getWindowLayoutComponent())); + } + + @VisibleForTesting + SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) { final MainThreadExecutor executor = new MainThreadExecutor(); mHandler = executor.mHandler; mPresenter = new SplitPresenter(executor, this); - ActivityThread activityThread = ActivityThread.currentActivityThread(); + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + final Application application = activityThread.getApplication(); // Register a callback to be notified about activities being created. - activityThread.getApplication().registerActivityLifecycleCallbacks( - new LifecycleCallbacks()); + application.registerActivityLifecycleCallbacks(new LifecycleCallbacks()); // Intercept activity starts to route activities to new containers if necessary. Instrumentation instrumentation = activityThread.getInstrumentation(); + mActivityStartMonitor = new ActivityStartMonitor(); instrumentation.addMonitor(mActivityStartMonitor); + mWindowLayoutComponent = windowLayoutComponent; + mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener()); + } + + private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> { + @Override + public void accept(List<CommonFoldingFeature> foldingFeatures) { + synchronized (mLock) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + for (int i = 0; i < mTaskContainers.size(); i++) { + final TaskContainer taskContainer = mTaskContainers.valueAt(i); + if (!taskContainer.isVisible()) { + continue; + } + if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) { + continue; + } + // TODO(b/238948678): Support reporting display features in all windowing modes. + if (taskContainer.isInMultiWindow()) { + continue; + } + if (taskContainer.isEmpty()) { + continue; + } + updateContainersInTask(wct, taskContainer); + updateAnimationOverride(taskContainer); + } + mPresenter.applyTransaction(wct); + } + } } /** Updates the embedding rules applied to future activity launches. */ @@ -141,12 +205,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) { - // TODO: Implement this method + synchronized (mLock) { + mSplitAttributesCalculator = calculator; + } } @Override public void clearSplitAttributesCalculator() { - // TODO: Implement this method + synchronized (mLock) { + mSplitAttributesCalculator = null; + } + } + + @GuardedBy("mLock") + @Nullable + SplitAttributesCalculator getSplitAttributesCalculator() { + return mSplitAttributesCalculator; } @NonNull @@ -191,7 +265,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen onTaskFragmentVanished(wct, info); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: - onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration()); + onTaskFragmentParentInfoChanged(wct, taskId, + change.getTaskFragmentParentInfo()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); @@ -346,22 +421,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId Id of the parent Task that is changed. - * @param parentConfig Config of the parent Task. + * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, - int taskId, @NonNull Configuration parentConfig) { - onTaskConfigurationChanged(taskId, parentConfig); - if (isInPictureInPicture(parentConfig)) { - // No need to update presentation in PIP until the Task exit PIP. - return; - } + int taskId, @NonNull TaskFragmentParentInfo parentInfo) { final TaskContainer taskContainer = getTaskContainer(taskId); if (taskContainer == null || taskContainer.isEmpty()) { Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); return; } + taskContainer.updateTaskFragmentParentInfo(parentInfo); + if (!taskContainer.isVisible()) { + // Don't update containers if the task is not visible. We only update containers when + // parentInfo#isVisibleRequested is true. + return; + } + onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration()); + if (isInPictureInPicture(parentInfo.getConfiguration())) { + // No need to update presentation in PIP until the Task exit PIP. + return; + } + updateContainersInTask(wct, taskContainer); + } + + private void updateContainersInTask(@NonNull WindowContainerTransaction wct, + @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. final List<TaskFragmentContainer> containers = @@ -486,6 +572,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ + @GuardedBy("mLock") private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); @@ -501,14 +588,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) { - final TaskContainer taskContainer = mTaskContainers.get(taskId); - if (taskContainer == null) { - return; - } + @GuardedBy("mLock") + private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer, + @NonNull Configuration config) { final boolean wasInPip = taskContainer.isInPictureInPicture(); final boolean isInPIp = isInPictureInPicture(config); - taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; @@ -526,37 +610,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Updates if we should override transition animation. We only want to override if the Task * bounds is large enough for at least one split rule. */ + @GuardedBy("mLock") private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { if (ENABLE_SHELL_TRANSITIONS) { // TODO(b/207070762): cleanup with legacy app transition // Animation will be handled by WM Shell with Shell transition enabled. return; } - if (!taskContainer.isTaskBoundsInitialized() - || !taskContainer.isWindowingModeInitialized()) { + if (!taskContainer.isTaskBoundsInitialized()) { // We don't know about the Task bounds/windowingMode yet. return; } - // We only want to override if it supports split. - if (supportSplit(taskContainer)) { + // We only want to override if the TaskContainer may show split. + if (mayShowSplit(taskContainer)) { mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId()); } else { mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); } } - private boolean supportSplit(@NonNull TaskContainer taskContainer) { + /** Returns whether the given {@link TaskContainer} may show in split. */ + // Suppress GuardedBy warning because lint asks to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") + private boolean mayShowSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (taskContainer.isInPictureInPicture()) { return false; } + // Always assume the TaskContainer if SplitAttributesCalculator is set + if (mSplitAttributesCalculator != null) { + return true; + } // Check if the parent container bounds can support any split rule. for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitRule)) { continue; } - if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { + final SplitRule splitRule = (SplitRule) rule; + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes( + taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */); + if (shouldShowSplit(splitAttributes)) { return true; } } @@ -700,14 +796,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Starts an activity to side of the launchingActivity with the provided split config. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") private void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { + @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, + boolean isPlaceholder) { try { mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, - isPlaceholder); + splitAttributes, isPlaceholder); } catch (Exception e) { if (failureCallback != null) { failureCallback.accept(e); @@ -734,6 +834,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Whether the given new launched activity is in a split with a rule matched. */ + // Suppress GuardedBy warning because lint asks to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) { final TaskFragmentContainer container = getContainerWithActivity(launchedActivity); final SplitContainer splitContainer = getActiveSplitForContainer(container); @@ -827,8 +931,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer primaryContainer = getContainerWithActivity( primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); + final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() - && canReuseContainer(splitRule, splitContainer.getSplitRule())) { + && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); @@ -958,6 +1063,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @VisibleForTesting @Nullable + @GuardedBy("mLock") TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { /* @@ -1020,6 +1126,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into. */ + @GuardedBy("mLock") @Nullable private TaskFragmentContainer createEmptyExpandedContainer( @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @@ -1061,8 +1168,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); + final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() - && (canReuseContainer(splitRule, splitContainer.getSplitRule()) + && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop) && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, @@ -1101,12 +1209,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); } + @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, activityInTask, taskId); } + @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, @@ -1130,7 +1240,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen throw new IllegalArgumentException("activityInTask must not be null,"); } if (!mTaskContainers.contains(taskId)) { - mTaskContainers.put(taskId, new TaskContainer(taskId)); + mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, @@ -1142,10 +1252,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } } - if (!taskContainer.isWindowingModeInitialized()) { - taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() - .windowConfiguration.getWindowingMode()); - } updateAnimationOverride(taskContainer); return container; } @@ -1154,12 +1260,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Creates and registers a new split with the provided containers and configuration. Finishes * existing secondary containers if found for the given primary container. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") void registerSplit(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, - @NonNull SplitRule splitRule) { + @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) { final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, - secondaryContainer, splitRule); + secondaryContainer, splitRule, splitAttributes); // Remove container later to prevent pinning escaping toast showing in lock task mode. if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); @@ -1310,6 +1420,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Skip position update - one or both containers are finished. return; } + final TaskContainer taskContainer = splitContainer.getTaskContainer(); + final SplitRule splitRule = splitContainer.getSplitRule(); + final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes( + taskContainer.getTaskProperties(), splitRule, minDimensionsPair); + splitContainer.setSplitAttributes(splitAttributes); if (dismissPlaceholderIfNecessary(wct, splitContainer)) { // Placeholder was finished, the positions will be updated when its container is emptied return; @@ -1383,6 +1499,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); } + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated) { @@ -1409,18 +1528,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity); final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, placeholderRule.getPlaceholderIntent()); - if (!shouldShowSideBySide( - mPresenter.getParentContainerBounds(activity), placeholderRule, - minDimensionsPair)) { + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties, + placeholderRule, minDimensionsPair); + if (!SplitPresenter.shouldShowSplit(splitAttributes)) { return false; } // TODO(b/190433398): Handle failed request final Bundle options = getPlaceholderOptions(activity, isOnCreated); startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, - placeholderRule, null /* failureCallback */, true /* isPlaceholder */); + placeholderRule, splitAttributes, null /* failureCallback */, + true /* isPlaceholder */); return true; } @@ -1445,6 +1566,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return options.toBundle(); } + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, @@ -1457,11 +1581,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // The placeholder should remain after it was first shown. return false; } - - if (shouldShowSideBySide(splitContainer)) { + final SplitAttributes splitAttributes = splitContainer.getSplitAttributes(); + if (SplitPresenter.shouldShowSplit(splitAttributes)) { return false; } - mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), false /* shouldFinishDependent */); return true; @@ -1471,6 +1594,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns the rule to launch a placeholder for the activity with the provided component name * if it is configured in the split config. */ + @GuardedBy("mLock") private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) { for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitPlaceholderRule)) { @@ -1487,6 +1611,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Notifies listeners about changes to split states if necessary. */ + @GuardedBy("mLock") private void updateCallbackIfNecessary() { if (mEmbeddingCallback == null) { return; @@ -1508,6 +1633,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * null, that indicates that the active split states are in an intermediate state and should * not be reported. */ + @GuardedBy("mLock") @Nullable private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); @@ -1526,20 +1652,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .toActivityStack(); final ActivityStack secondaryContainer = container.getSecondaryContainer() .toActivityStack(); - final SplitAttributes.SplitType splitType = shouldShowSideBySide(container) - ? new SplitAttributes.SplitType.RatioSplitType( - container.getSplitRule().getSplitRatio()) - : new SplitAttributes.SplitType.ExpandContainersSplitType(); final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer, - // Splits that are not showing side-by-side are reported as having 0 split - // ratio, since by definition in the API the primary container occupies no - // width of the split when covered by the secondary. - // TODO(b/241042437): use v2 APIs for splitAttributes - new SplitAttributes.Builder() - .setSplitType(splitType) - .setLayoutDirection(container.getSplitRule().getLayoutDirection()) - .build() - ); + container.getSplitAttributes()); splitStates.add(splitState); } } @@ -1577,6 +1691,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns a split rule for the provided pair of primary activity and secondary activity intent * if available. */ + @GuardedBy("mLock") @Nullable private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent) { @@ -1595,6 +1710,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns a split rule for the provided pair of primary and secondary activities if available. */ + @GuardedBy("mLock") @Nullable private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -1669,6 +1785,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. */ + @GuardedBy("mLock") private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof ActivityRule)) { @@ -1694,6 +1811,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * 'sticky' and the placeholder was finished when fully overlapping the primary container. * @return {@code true} if the associated container should be retained (and not be finished). */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer) { SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, @@ -1712,7 +1833,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Decide whether the associated container should be retained based on the current // presentation mode. - if (shouldShowSideBySide(splitContainer)) { + if (shouldShowSplit(splitContainer)) { return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); } else { return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); @@ -1905,23 +2026,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if * there is any. */ - private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) { + private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2, + @NonNull WindowMetrics parentWindowMetrics) { if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { return false; } - return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2); + return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, + parentWindowMetrics); } /** Whether the two rules have the same presentation. */ - private static boolean haveSamePresentation(@NonNull SplitPairRule rule1, - @NonNull SplitPairRule rule2) { + @VisibleForTesting + static boolean haveSamePresentation(@NonNull SplitPairRule rule1, + @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { + if (rule1.getTag() != null || rule2.getTag() != null) { + // Tag must be unique if it is set. We don't want to reuse the container if the rules + // have different tags because they can have different SplitAttributes later through + // SplitAttributesCalculator. + return Objects.equals(rule1.getTag(), rule2.getTag()); + } + // If both rules don't have tag, compare all SplitRules' properties that may affect their + // SplitAttributes. // TODO(b/231655482): add util method to do the comparison in SplitPairRule. - return rule1.getSplitRatio() == rule2.getSplitRatio() - && rule1.getLayoutDirection() == rule2.getLayoutDirection() - && rule1.getFinishPrimaryWithSecondary() - == rule2.getFinishPrimaryWithSecondary() - && rule1.getFinishSecondaryWithPrimary() - == rule2.getFinishSecondaryWithPrimary(); + return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes()) + && rule1.checkParentMetrics(parentWindowMetrics) + == rule2.checkParentMetrics(parentWindowMetrics) + && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary() + && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary(); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 2ef8e4c64855..79603233ae14 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -22,11 +22,11 @@ import android.app.Activity; import android.app.ActivityThread; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; -import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -42,9 +42,21 @@ import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType; +import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams; +import androidx.window.extensions.embedding.TaskContainer.TaskProperties; +import androidx.window.extensions.layout.DisplayFeature; +import androidx.window.extensions.layout.FoldingFeature; +import androidx.window.extensions.layout.WindowLayoutInfo; import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -66,11 +78,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { }) private @interface Position {} + private static final int CONTAINER_POSITION_LEFT = 0; + private static final int CONTAINER_POSITION_TOP = 1; + private static final int CONTAINER_POSITION_RIGHT = 2; + private static final int CONTAINER_POSITION_BOTTOM = 3; + + @IntDef(value = { + CONTAINER_POSITION_LEFT, + CONTAINER_POSITION_TOP, + CONTAINER_POSITION_RIGHT, + CONTAINER_POSITION_BOTTOM, + }) + private @interface ContainerPosition {} + /** * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, * Activity, Activity, Intent)}. * No need to expand the splitContainer because screen is big enough to - * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied. + * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is + * satisfied. */ static final int RESULT_NOT_EXPANDED = 0; /** @@ -78,7 +104,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Activity, Activity, Intent)}. * The splitContainer should be expanded. It is usually because minimum dimensions is not * satisfied. - * @see #shouldShowSideBySide(Rect, SplitRule, Pair) + * @see #shouldShowSplit(SplitAttributes) */ static final int RESULT_EXPANDED = 1; /** @@ -101,6 +127,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { }) private @interface ResultCode {} + @VisibleForTesting + static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES = + new SplitAttributes.Builder() + .setSplitType(new ExpandContainersSplitType()) + .build(); + private final SplitController mController; SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) { @@ -129,14 +161,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @return The newly created secondary container. */ @NonNull + @GuardedBy("mController.mLock") TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { - final Rect parentBounds = getParentContainerBounds(primaryActivity); + final TaskProperties taskProperties = getTaskProperties(primaryActivity); final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( primaryActivity, secondaryIntent); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - primaryActivity, minDimensionsPair); + final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, + minDimensionsPair); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); @@ -144,8 +179,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( secondaryIntent, primaryActivity, taskId); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, - rule, primaryActivity, minDimensionsPair); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), @@ -154,9 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); - mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); + mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, + splitAttributes); return secondaryContainer; } @@ -176,16 +212,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { - final Rect parentBounds = getParentContainerBounds(primaryActivity); + final TaskProperties taskProperties = getTaskProperties(primaryActivity); final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - primaryActivity, minDimensionsPair); + final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, + minDimensionsPair); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - primaryActivity, minDimensionsPair); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; @@ -200,9 +238,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); - mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); + mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, + splitAttributes); } /** @@ -244,16 +283,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param rule The split rule to be applied to the container. * @param isPlaceholder Whether the launch is a placeholder. */ + @GuardedBy("mController.mLock") void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { - final Rect parentBounds = getParentContainerBounds(launchingActivity); - final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( - launchingActivity, activityIntent); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - launchingActivity, minDimensionsPair); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - launchingActivity, minDimensionsPair); + @Nullable Bundle activityOptions, @NonNull SplitRule rule, + @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) { + final TaskProperties taskProperties = getTaskProperties(launchingActivity); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); @@ -268,7 +307,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, - rule); + rule, splitAttributes); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, activityIntent, activityOptions, rule, windowingMode); @@ -284,22 +323,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param updatedContainer The task fragment that was updated and caused this split update. * @param wct WindowContainerTransaction that this update should be performed with. */ + @GuardedBy("mController.mLock") void updateSplitContainer(@NonNull SplitContainer splitContainer, @NonNull TaskFragmentContainer updatedContainer, @NonNull WindowContainerTransaction wct) { - // Getting the parent bounds using the updated container - it will have the recent value. - final Rect parentBounds = getParentContainerBounds(updatedContainer); + // Getting the parent configuration using the updated container - it will have the recent + // value. final SplitRule rule = splitContainer.getSplitRule(); final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); final Activity activity = primaryContainer.getTopNonFinishingActivity(); if (activity == null) { return; } - final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - activity, minDimensionsPair); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - activity, minDimensionsPair); + final TaskProperties taskProperties = getTaskProperties(updatedContainer); + final SplitAttributes splitAttributes = splitContainer.getSplitAttributes(); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); // Whether the placeholder is becoming side-by-side with the primary from fullscreen. final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() @@ -311,7 +352,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); if (isPlaceholderBecomingSplit) { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -323,14 +364,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } + @GuardedBy("mController.mLock") private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, - @NonNull Pair<Size, Size> minDimensionsPair) { - final Rect parentBounds = getParentContainerBounds(primaryContainer); + @NonNull SplitAttributes splitAttributes) { // Clear adjacent TaskFragments if the container is shown in fullscreen, or the // secondaryContainer could not be finished. - if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) { + if (!shouldShowSplit(splitAttributes)) { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), null /* secondary */, null /* splitRule */); } else { @@ -416,8 +457,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Expands the split container if the current split bounds are smaller than the Activity or * Intent that is added to the container. * - * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} - * and if {@link android.window.TaskFragmentInfo} has reported to the client side. + * @return the {@link ResultCode} based on + * {@link #shouldShowSplit(SplitAttributes)} and if + * {@link android.window.TaskFragmentInfo} has reported to the client side. */ @ResultCode int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct, @@ -427,7 +469,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be" + " non-null."); } - final Rect taskBounds = getParentContainerBounds(primaryActivity); final Pair<Size, Size> minDimensionsPair; if (secondaryActivity != null) { minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); @@ -436,7 +477,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryIntent); } // Expand the splitContainer if minimum dimensions are not satisfied. - if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) { + final TaskContainer taskContainer = splitContainer.getTaskContainer(); + final SplitAttributes splitAttributes = sanitizeSplitAttributes( + taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(), + minDimensionsPair); + splitContainer.setSplitAttributes(splitAttributes); + if (!shouldShowSplit(splitAttributes)) { // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment // bounds. Return failure to create a new SplitContainer which fills task bounds. if (splitContainer.getPrimaryContainer().getInfo() == null @@ -450,36 +496,63 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return RESULT_NOT_EXPANDED; } - static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { - return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); + static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) { + return shouldShowSplit(splitContainer.getSplitAttributes()); } - static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { - final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); + static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) { + return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType); + } - return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(), - splitContainer.getMinDimensionsPair()); + @GuardedBy("mController.mLock") + @NonNull + SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties, + @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) { + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration); + final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator(); + final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes(); + final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics); + if (calculator == null) { + if (!isDefaultMinSizeSatisfied) { + return EXPAND_CONTAINERS_ATTRIBUTES; + } + return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, + minDimensionsPair); + } + final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent + .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), + taskConfiguration.windowConfiguration); + final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( + taskWindowMetrics, taskConfiguration, defaultSplitAttributes, + isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag()); + final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params); + return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair); } - static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule, + /** + * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't + * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns + * the passed {@link SplitAttributes}. + */ + @NonNull + private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes, @Nullable Pair<Size, Size> minDimensionsPair) { - // TODO(b/190433398): Supply correct insets. - final WindowMetrics parentMetrics = new WindowMetrics(parentBounds, - new WindowInsets(new Rect())); - // Don't show side by side if bounds is not qualified. - if (!rule.checkParentMetrics(parentMetrics)) { - return false; - } - final float splitRatio = rule.getSplitRatio(); - // We only care the size of the bounds regardless of its position. - final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */); - final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */); - if (minDimensionsPair == null) { - return true; - } - return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) - && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second); + return splitAttributes; + } + final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes, + foldingFeature); + final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes, + foldingFeature); + if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) + || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) { + return EXPAND_CONTAINERS_ATTRIBUTES; + } + return splitAttributes; } @NonNull @@ -541,20 +614,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @VisibleForTesting @NonNull - static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, - @NonNull SplitRule rule, @NonNull Activity primaryActivity, - @Nullable Pair<Size, Size> minDimensionsPair) { - if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) { + Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes) { + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration, + foldingFeature); + final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder() + .setSplitType(splitType) + .setLayoutDirection(splitAttributes.getLayoutDirection()) + .build(); + if (!shouldShowSplit(computedSplitAttributes)) { return new Rect(); } - final boolean isLtr = isLtr(primaryActivity, rule); - final float splitRatio = rule.getSplitRatio(); - switch (position) { case POSITION_START: - return getPrimaryBounds(parentBounds, splitRatio, isLtr); + return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature); case POSITION_END: - return getSecondaryBounds(parentBounds, splitRatio, isLtr); + return getSecondaryBounds(taskConfiguration, computedSplitAttributes, + foldingFeature); case POSITION_FILL: default: return new Rect(); @@ -562,74 +640,303 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio, - boolean isLtr) { - return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) - : getRightContainerBounds(parentBounds, 1 - splitRatio); + private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + if (!shouldShowSplit(splitAttributes)) { + return new Rect(); + } + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { + return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { + return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.LOCALE: { + final boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr + ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature) + : getRightContainerBounds(taskConfiguration, splitAttributes, + foldingFeature); + } + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { + return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { + return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + default: + throw new IllegalArgumentException("Unknown layout direction:" + + splitAttributes.getLayoutDirection()); + } } @NonNull - private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio, - boolean isLtr) { - return isLtr ? getRightContainerBounds(parentBounds, splitRatio) - : getLeftContainerBounds(parentBounds, 1 - splitRatio); + private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + if (!shouldShowSplit(splitAttributes)) { + return new Rect(); + } + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { + return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { + return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.LOCALE: { + final boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr + ? getRightContainerBounds(taskConfiguration, splitAttributes, + foldingFeature) + : getLeftContainerBounds(taskConfiguration, splitAttributes, + foldingFeature); + } + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { + return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { + return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + default: + throw new IllegalArgumentException("Unknown layout direction:" + + splitAttributes.getLayoutDirection()); + } } - private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { - return new Rect( - parentBounds.left, - parentBounds.top, - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.bottom); + @NonNull + private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_LEFT, foldingFeature); + final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom); } - private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { - return new Rect( - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.top, - parentBounds.right, - parentBounds.bottom); + @NonNull + private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_RIGHT, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom); + } + + @NonNull + private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_TOP, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom); + } + + @NonNull + private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_BOTTOM, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom); } /** - * Checks if a split with the provided rule should be displays in left-to-right layout - * direction, either always or with the current configuration. + * Computes the boundary position between the primary and the secondary containers for the given + * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states. + * <ol> + * <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom + * container, which is {@link Rect#bottom} of the top container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top + * container, which is {@link Rect#top} of the bottom container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right + * container, which is {@link Rect#right} of the left container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom + * container, which is {@link Rect#left} of the right container bounds.</li> + * </ol> + * + * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature) */ - private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { - switch (rule.getLayoutDirection()) { - case LayoutDirection.LOCALE: - return context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; - case LayoutDirection.RTL: - return false; - case LayoutDirection.LTR: + private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @ContainerPosition int position, + @Nullable FoldingFeature foldingFeature) { + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + final int startPoint = shouldSplitHorizontally(splitAttributes) + ? parentBounds.top + : parentBounds.left; + final int dimen = shouldSplitHorizontally(splitAttributes) + ? parentBounds.height() + : parentBounds.width(); + final SplitType splitType = splitAttributes.getSplitType(); + if (splitType instanceof RatioSplitType) { + final RatioSplitType splitRatio = (RatioSplitType) splitType; + return (int) (startPoint + dimen * splitRatio.getRatio()); + } + // At this point, SplitType must be a HingeSplitType and foldingFeature must be + // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier. + Objects.requireNonNull(foldingFeature); + if (!(splitType instanceof HingeSplitType)) { + throw new IllegalArgumentException("Unknown splitType:" + splitType); + } + final Rect hingeArea = foldingFeature.getBounds(); + switch (position) { + case CONTAINER_POSITION_LEFT: + return hingeArea.left; + case CONTAINER_POSITION_TOP: + return hingeArea.top; + case CONTAINER_POSITION_RIGHT: + return hingeArea.right; + case CONTAINER_POSITION_BOTTOM: + return hingeArea.bottom; default: - return true; + throw new IllegalArgumentException("Unknown position:" + position); } } - @NonNull - static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { - return container.getTaskContainer().getTaskBounds(); + @Nullable + private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) { + final int displayId = taskProperties.getDisplayId(); + final WindowConfiguration windowConfiguration = taskProperties.getConfiguration() + .windowConfiguration; + final WindowLayoutInfo info = mController.mWindowLayoutComponent + .getCurrentWindowLayoutInfo(displayId, windowConfiguration); + final List<DisplayFeature> displayFeatures = info.getDisplayFeatures(); + if (displayFeatures.isEmpty()) { + return null; + } + final List<FoldingFeature> foldingFeatures = new ArrayList<>(); + for (DisplayFeature displayFeature : displayFeatures) { + if (displayFeature instanceof FoldingFeature) { + foldingFeatures.add((FoldingFeature) displayFeature); + } + } + // TODO(b/240219484): Support device with multiple hinges. + if (foldingFeatures.size() != 1) { + return null; + } + return foldingFeatures.get(0); } - @NonNull - Rect getParentContainerBounds(@NonNull Activity activity) { - final TaskFragmentContainer container = mController.getContainerWithActivity(activity); - if (container != null) { - return getParentContainerBounds(container); + /** + * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns + * {@code false} if this {@link SplitAttributes} splits the task vertically. + */ + private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) { + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: + return true; + default: + return false; } - // Obtain bounds from Activity instead because the Activity hasn't been embedded yet. - return getNonEmbeddedActivityBounds(activity); } /** - * Obtains the bounds from a non-embedded Activity. - * <p> - * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most - * cases unless we want to obtain task bounds before - * {@link TaskContainer#isTaskBoundsInitialized()}. + * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and + * window state. + * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed + * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is + * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or + * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier. + * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks + * the current device and window states to determine whether the split container should split + * by hinge or use {@link HingeSplitType#getFallbackSplitType}. */ + private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes, + @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) { + final int layoutDirection = splitAttributes.getLayoutDirection(); + final SplitType splitType = splitAttributes.getSplitType(); + if (splitType instanceof ExpandContainersSplitType) { + return splitType; + } else if (splitType instanceof RatioSplitType) { + final RatioSplitType splitRatio = (RatioSplitType) splitType; + // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary + // computation have the same direction, which is from (top, left) to (bottom, right). + final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio()); + switch (layoutDirection) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: + return splitType; + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: + return reversedSplitType; + case LayoutDirection.LOCALE: { + boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr ? splitType : reversedSplitType; + } + } + } else if (splitType instanceof HingeSplitType) { + final HingeSplitType hinge = (HingeSplitType) splitType; + @WindowingMode + final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode(); + return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode) + ? hinge : hinge.getFallbackSplitType(); + } + throw new IllegalArgumentException("Unknown SplitType:" + splitType); + } + + private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes, + @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) { + // Only HingeSplitType may split the task bounds by hinge. + if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) { + return false; + } + // Device is not foldable, so there's no hinge to match. + if (foldingFeature == null) { + return false; + } + // The task is in multi-window mode. Match hinge doesn't make sense because current task + // bounds may not fit display bounds. + if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) { + return false; + } + // Return true if how the split attributes split the task bounds matches the orientation of + // folding area orientation. + return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature); + } + + private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) { + final Rect bounds = foldingFeature.getBounds(); + return bounds.width() > bounds.height(); + } + + @NonNull + static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) { + return container.getTaskContainer().getTaskProperties(); + } + + @NonNull + TaskProperties getTaskProperties(@NonNull Activity activity) { + final TaskContainer taskContainer = mController.getTaskContainer( + mController.getTaskId(activity)); + if (taskContainer != null) { + return taskContainer.getTaskProperties(); + } + // Use a copy of configuration because activity's configuration may be updated later, + // or we may get unexpected TaskContainer's configuration if Activity's configuration is + // updated. An example is Activity is going to be in split. + return new TaskProperties(activity.getDisplayId(), + new Configuration(activity.getResources().getConfiguration())); + } + + @NonNull + WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) { + return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration()); + } + + @NonNull + private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { + final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); + // TODO(b/190433398): Supply correct insets. + return new WindowMetrics(taskBounds, WindowInsets.CONSUMED); + } + + /** Obtains the bounds from a non-embedded Activity. */ @NonNull static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) { final WindowConfiguration windowConfiguration = diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index b5636777568e..91573ffef568 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -24,10 +24,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,13 +45,10 @@ class TaskContainer { /** The unique task id. */ private final int mTaskId; + // TODO(b/240219484): consolidate to mConfiguration /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); - /** Windowing mode of this Task. */ - @WindowingMode - private int mWindowingMode = WINDOWING_MODE_UNDEFINED; - /** Active TaskFragments in this Task. */ @NonNull final List<TaskFragmentContainer> mContainers = new ArrayList<>(); @@ -57,24 +57,56 @@ class TaskContainer { @NonNull final List<SplitContainer> mSplitContainers = new ArrayList<>(); + @NonNull + private final Configuration mConfiguration; + + private int mDisplayId; + + private boolean mIsVisible; + /** * TaskFragments that the organizer has requested to be closed. They should be removed when - * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event - * for them. + * the organizer receives + * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)} + * event for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); - TaskContainer(int taskId) { + /** + * The {@link TaskContainer} constructor + * + * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with + * {@code activityInTask}. + * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to + * initialize the {@link TaskContainer} properties. + * + */ + TaskContainer(int taskId, @NonNull Activity activityInTask) { if (taskId == INVALID_TASK_ID) { throw new IllegalArgumentException("Invalid Task id"); } mTaskId = taskId; + // Make a copy in case the activity's config is updated, and updates the TaskContainer's + // config unexpectedly. + mConfiguration = new Configuration(activityInTask.getResources().getConfiguration()); + mDisplayId = activityInTask.getDisplayId(); + // Note that it is always called when there's a new Activity is started, which implies + // the host task is visible. + mIsVisible = true; } int getTaskId() { return mTaskId; } + int getDisplayId() { + return mDisplayId; + } + + boolean isVisible() { + return mIsVisible; + } + @NonNull Rect getTaskBounds() { return mTaskBounds; @@ -94,13 +126,21 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } - void setWindowingMode(int windowingMode) { - mWindowingMode = windowingMode; + @NonNull + Configuration getConfiguration() { + // Make a copy in case the config is updated unexpectedly. + return new Configuration(mConfiguration); + } + + @NonNull + TaskProperties getTaskProperties() { + return new TaskProperties(mDisplayId, mConfiguration); } - /** Whether the Task windowing mode has been initialized. */ - boolean isWindowingModeInitialized() { - return mWindowingMode != WINDOWING_MODE_UNDEFINED; + void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mConfiguration.setTo(info.getConfiguration()); + mDisplayId = info.getDisplayId(); + mIsVisible = info.isVisibleRequested(); } /** @@ -123,13 +163,20 @@ class TaskContainer { // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the // Task windowing mode if the Task is in multi window. // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. - return WindowConfiguration.inMultiWindowMode(mWindowingMode) - ? mWindowingMode - : WINDOWING_MODE_MULTI_WINDOW; + return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW; } boolean isInPictureInPicture() { - return mWindowingMode == WINDOWING_MODE_PINNED; + return getWindowingMode() == WINDOWING_MODE_PINNED; + } + + boolean isInMultiWindow() { + return WindowConfiguration.inMultiWindowMode(getWindowingMode()); + } + + @WindowingMode + private int getWindowingMode() { + return getConfiguration().windowConfiguration.getWindowingMode(); } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ @@ -173,4 +220,28 @@ class TaskContainer { int indexOf(@NonNull TaskFragmentContainer child) { return mContainers.indexOf(child); } + + /** + * A wrapper class which contains the display ID and {@link Configuration} of a + * {@link TaskContainer} + */ + static final class TaskProperties { + private final int mDisplayId; + @NonNull + private final Configuration mConfiguration; + + TaskProperties(int displayId, @NonNull Configuration configuration) { + mDisplayId = displayId; + mConfiguration = configuration; + } + + int getDisplayId() { + return mDisplayId; + } + + @NonNull + Configuration getConfiguration() { + return mConfiguration; + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 626e0d990a6d..18712aed1be6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -432,7 +432,7 @@ class TaskFragmentContainer { // In case we have requested to reparent the activity to another container (as // pendingAppeared), we don't want to finish it with this container. && mController.getContainerWithActivity(activity) == this) { - activity.finish(); + wct.finishActivity(activity.getActivityToken()); } } @@ -457,7 +457,7 @@ class TaskFragmentContainer { || controller.shouldRetainAssociatedActivity(this, activity)) { continue; } - activity.finish(); + wct.finishActivity(activity.getActivityToken()); } mActivitiesToFinishOnExit.clear(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index f24401f0cd53..c76f568e117f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -47,6 +47,7 @@ import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.DataProducer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -68,6 +69,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; + private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>(); + private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners = new ArrayMap<>(); @@ -80,6 +83,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } + /** Registers to listen to {@link CommonFoldingFeature} changes */ + public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) { + mFoldingFeatureProducer.addDataChangedCallback(consumer); + } + /** * Adds a listener interested in receiving updates to {@link WindowLayoutInfo} * @@ -186,6 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { + mLastReportedFoldingFeatures.clear(); + mLastReportedFoldingFeatures.addAll(storedFeatures); for (Context context : getContextsListeningForLayoutChanges()) { // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context); @@ -207,6 +217,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } /** + * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}. + * + * @return current {@link WindowLayoutInfo} on the default display. Returns + * empty {@link WindowLayoutInfo} on secondary displays. + */ + @NonNull + public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId, + @NonNull WindowConfiguration windowConfiguration) { + return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures); + } + + /** @see #getWindowLayoutInfo(Context, List) */ + private WindowLayoutInfo getWindowLayoutInfo(int displayId, + @NonNull WindowConfiguration windowConfiguration, + List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration, + storedFeatures); + return new WindowLayoutInfo(displayFeatureList); + } + + /** * Translate from the {@link CommonFoldingFeature} to * {@link DisplayFeature} for a given {@link Activity}. If a * {@link CommonFoldingFeature} is not valid then it will be omitted. @@ -225,12 +256,23 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { */ private List<DisplayFeature> getDisplayFeatures( @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) { - List<DisplayFeature> features = new ArrayList<>(); if (!shouldReportDisplayFeatures(context)) { + return Collections.emptyList(); + } + return getDisplayFeatures(context.getDisplayId(), + context.getResources().getConfiguration().windowConfiguration, + storedFeatures); + } + + /** @see #getDisplayFeatures(Context, List) */ + private List<DisplayFeature> getDisplayFeatures(int displayId, + @NonNull WindowConfiguration windowConfiguration, + List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> features = new ArrayList<>(); + if (displayId != DEFAULT_DISPLAY) { return features; } - int displayId = context.getDisplay().getDisplayId(); for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { @@ -238,7 +280,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(context, featureRect); + transformToWindowSpaceRect(windowConfiguration, featureRect); if (!isZero(featureRect)) { // TODO(b/228641877): Remove guarding when fixed. @@ -263,6 +305,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { windowingMode = ActivityClient.getInstance().getTaskWindowingMode( context.getActivityToken()); } else { + // TODO(b/242674941): use task windowing mode for window context that associates with + // activity. windowingMode = context.getResources().getConfiguration().windowConfiguration .getWindowingMode(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 31bf96313a95..9e2611f392a3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -21,6 +21,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; @@ -89,13 +90,21 @@ public final class ExtensionHelper { /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ public static void transformToWindowSpaceRect(@NonNull @UiContext Context context, Rect inOutRect) { - Rect windowRect = getWindowBounds(context); - if (!Rect.intersects(inOutRect, windowRect)) { + transformToWindowSpaceRect(getWindowBounds(context), inOutRect); + } + + /** @see ExtensionHelper#transformToWindowSpaceRect(Context, Rect) */ + public static void transformToWindowSpaceRect(@NonNull WindowConfiguration windowConfiguration, + Rect inOutRect) { + transformToWindowSpaceRect(windowConfiguration.getBounds(), inOutRect); + } + + private static void transformToWindowSpaceRect(@NonNull Rect bounds, @NonNull Rect inOutRect) { + if (!inOutRect.intersect(bounds)) { inOutRect.setEmpty(); return; } - inOutRect.intersect(windowRect); - inOutRect.offset(-windowRect.left, -windowRect.top); + inOutRect.offset(-bounds.left, -bounds.top); } /** diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index effc1a3ef3ea..40f7a273980a 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -16,9 +16,12 @@ package androidx.window.extensions.embedding; +import static android.view.Display.DEFAULT_DISPLAY; + import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.annotation.NonNull; @@ -26,32 +29,68 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.util.Pair; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import androidx.window.extensions.layout.DisplayFeature; +import androidx.window.extensions.layout.FoldingFeature; +import androidx.window.extensions.layout.WindowLayoutInfo; + +import java.util.ArrayList; import java.util.Collections; +import java.util.List; public class EmbeddingTestUtils { static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); static final int TASK_ID = 10; - static final float SPLIT_RATIO = 0.5f; + static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually(); + static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build(); + static final String TEST_TAG = "test"; /** Default finish behavior in Jetpack. */ static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; + private static final float SPLIT_RATIO = 0.5f; private EmbeddingTestUtils() {} /** Gets the bounds of a TaskFragment that is in split. */ static Rect getSplitBounds(boolean isPrimary) { - final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); + return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */); + } + + /** Gets the bounds of a TaskFragment that is in split. */ + static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) { + final int dimension = (int) ( + (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width()) + * SPLIT_RATIO); + if (shouldSplitHorizontally) { + return isPrimary + ? new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.top + dimension) + : new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + dimension, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom); + } return isPrimary - ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, - TASK_BOUNDS.bottom) + ? new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.left + dimension, + TASK_BOUNDS.bottom) : new Rect( - TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, + TASK_BOUNDS.left + dimension, + TASK_BOUNDS.top, + TASK_BOUNDS.right, TASK_BOUNDS.bottom); } @@ -69,10 +108,15 @@ public class EmbeddingTestUtils { activityPair -> false, targetPair::equals, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes( + new SplitAttributes.Builder() + .setSplitType(SPLIT_TYPE) + .build() + ) .setShouldClearTop(clearTop) .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setTag(TEST_TAG) .build(); } @@ -101,10 +145,15 @@ public class EmbeddingTestUtils { targetPair::equals, activityIntentPair -> false, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes( + new SplitAttributes.Builder() + .setSplitType(SPLIT_TYPE) + .build() + ) .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) .setShouldClearTop(clearTop) + .setTag(TEST_TAG) .build(); } @@ -130,4 +179,29 @@ public class EmbeddingTestUtils { primaryBounds.width() + 1, primaryBounds.height() + 1); return aInfo; } + + static TaskContainer createTestTaskContainer() { + Resources resources = mock(Resources.class); + doReturn(new Configuration()).when(resources).getConfiguration(); + Activity activity = mock(Activity.class); + doReturn(resources).when(activity).getResources(); + doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); + + return new TaskContainer(TASK_ID, activity); + } + + static WindowLayoutInfo createWindowLayoutInfo() { + final FoldingFeature foldingFeature = new FoldingFeature( + new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5, + TASK_BOUNDS.right, + TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5 + ), + FoldingFeature.TYPE_HINGE, + FoldingFeature.STATE_HALF_OPENED); + final List<DisplayFeature> displayFeatures = new ArrayList<>(); + displayFeatures.add(foldingFeature); + return new WindowLayoutInfo(displayFeatures); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 58a627bafa16..957a24873998 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -26,12 +27,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; +import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; @@ -65,7 +68,10 @@ public class JetpackTaskFragmentOrganizerTest { private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; + @Mock private SplitController mSplitController; + @Mock + private Handler mHandler; private JetpackTaskFragmentOrganizer mOrganizer; @Before @@ -73,9 +79,8 @@ public class JetpackTaskFragmentOrganizerTest { MockitoAnnotations.initMocks(this); mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback); mOrganizer.registerOrganizer(); - mSplitController = new SplitController(); spyOn(mOrganizer); - spyOn(mSplitController); + doReturn(mHandler).when(mSplitController).getHandler(); } @Test @@ -113,7 +118,7 @@ public class JetpackTaskFragmentOrganizerTest { @Test public void testExpandTaskFragment() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 58870a66feea..25d034756265 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_CANCELED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -27,15 +28,20 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -73,14 +79,19 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; @@ -116,6 +127,8 @@ public class SplitControllerTest { private WindowContainerTransaction mTransaction; @Mock private Handler mHandler; + @Mock + private WindowLayoutComponentImpl mWindowLayoutComponent; private SplitController mSplitController; private SplitPresenter mSplitPresenter; @@ -123,7 +136,9 @@ public class SplitControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mSplitController = new SplitController(); + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + mSplitController = new SplitController(mWindowLayoutComponent); mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); @@ -138,7 +153,7 @@ public class SplitControllerTest { @Test public void testGetTopActiveContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController); @@ -192,12 +207,13 @@ public class SplitControllerTest { verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); verify(mSplitController).removeContainer(tf); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); } @Test public void testOnTaskFragmentAppearEmptyTimeout() { final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); verify(mSplitPresenter).cleanupContainer(mTransaction, tf, @@ -268,6 +284,8 @@ public class SplitControllerTest { final SplitContainer splitContainer = mock(SplitContainer.class); doReturn(tf).when(splitContainer).getPrimaryContainer(); doReturn(tf).when(splitContainer).getSecondaryContainer(); + doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); + doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); final List<SplitContainer> splitContainers = mSplitController.getTaskContainer(TASK_ID).mSplitContainers; splitContainers.add(splitContainer); @@ -298,7 +316,7 @@ public class SplitControllerTest { // Verify if the top active split is updated if both of its containers are not finished. doReturn(false).when(mSplitController) - .dismissPlaceholderIfNecessary(mTransaction, splitContainer); + .dismissPlaceholderIfNecessary(mTransaction, splitContainer); mSplitController.updateContainer(mTransaction, tf); @@ -308,7 +326,7 @@ public class SplitControllerTest { @Test public void testOnStartActivityResultError() { final Intent intent = new Intent(); - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, intent, taskContainer, mSplitController); final SplitController.ActivityStartMonitor monitor = @@ -608,7 +626,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -624,7 +642,7 @@ public class SplitControllerTest { assertFalse(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), - anyBoolean()); + any(), anyBoolean()); } @Test @@ -641,7 +659,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -656,7 +674,7 @@ public class SplitControllerTest { assertFalse(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), - anyBoolean()); + any(), anyBoolean()); } @Test @@ -674,7 +692,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -693,14 +711,15 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - splitRule); + splitRule, + SPLIT_ATTRIBUTES); clearInvocations(mSplitController); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); - verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); + verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @Test @@ -720,7 +739,8 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - splitRule); + splitRule, + SPLIT_ATTRIBUTES); final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); @@ -741,7 +761,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); - verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); + verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @Test @@ -778,7 +798,8 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - placeholderRule); + placeholderRule, + SPLIT_ATTRIBUTES); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -983,9 +1004,9 @@ public class SplitControllerTest { assertTrue(primaryContainer.isFinished()); assertTrue(secondaryContainer0.isFinished()); assertTrue(secondaryContainer1.isFinished()); - verify(mActivity).finish(); - verify(secondaryActivity0).finish(); - verify(secondaryActivity1).finish(); + verify(mTransaction).finishActivity(mActivity.getActivityToken()); + verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); + verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); assertTrue(taskContainer.mContainers.isEmpty()); assertTrue(taskContainer.mSplitContainers.isEmpty()); } @@ -1038,15 +1059,16 @@ public class SplitControllerTest { @Test public void testOnTransactionReady_taskFragmentParentInfoChanged() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); - final Configuration taskConfig = new Configuration(); + final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, + DEFAULT_DISPLAY, true); transaction.addChange(new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(TASK_ID) - .setTaskConfiguration(taskConfig)); + .setTaskFragmentParentInfo(parentInfo)); mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID), - eq(taskConfig)); + eq(parentInfo)); verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), anyInt(), anyBoolean()); } @@ -1088,6 +1110,47 @@ public class SplitControllerTest { anyInt(), anyBoolean()); } + @Test + public void testHasSamePresentation() { + SplitPairRule splitRule1 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + SplitPairRule splitRule2 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + + assertTrue("Rules must have same presentation if tags are null and has same properties.", + SplitController.haveSamePresentation(splitRule1, splitRule2, + new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); + + splitRule2 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .setTag(TEST_TAG) + .build(); + + assertFalse("Rules must have different presentations if tags are not equal regardless" + + "of other properties", + SplitController.haveSamePresentation(splitRule1, splitRule2, + new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); + + + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class); @@ -1097,6 +1160,7 @@ public class SplitControllerTest { doReturn(activity).when(mSplitController).getActivity(activityToken); doReturn(TASK_ID).when(activity).getTaskId(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); return activity; } @@ -1135,7 +1199,7 @@ public class SplitControllerTest { private void setupPlaceholderRule(@NonNull Activity primaryActivity) { final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT, primaryActivity::equals, i -> false, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .build(); mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule)); } @@ -1188,7 +1252,8 @@ public class SplitControllerTest { primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer, - rule); + rule, + SPLIT_ATTRIBUTES); // We need to set those in case we are not respecting clear top. // TODO(b/231845476) we should always respect clearTop. diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 25f0e25eec75..6dae0a1086b3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -16,23 +16,28 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.Display.DEFAULT_DISPLAY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; +import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED; -import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; -import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -66,6 +71,8 @@ import android.window.WindowContainerTransaction; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; @@ -73,6 +80,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + /** * Test class for {@link SplitPresenter}. * @@ -94,13 +103,17 @@ public class SplitPresenterTest { private TaskFragmentInfo mTaskFragmentInfo; @Mock private WindowContainerTransaction mTransaction; + @Mock + private WindowLayoutComponentImpl mWindowLayoutComponent; private SplitController mController; private SplitPresenter mPresenter; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new SplitController(); + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + mController = new SplitController(mWindowLayoutComponent); mPresenter = mController.mPresenter; spyOn(mController); spyOn(mPresenter); @@ -162,59 +175,263 @@ public class SplitPresenterTest { @Test public void testShouldShowSideBySide() { - Activity secondaryActivity = createMockActivity(); - final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES)); + + final SplitAttributes expandContainers = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType()) + .build(); - assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule)); + assertFalse(SplitPresenter.shouldShowSplit(expandContainers)); + } - // Set minDimensions of primary container to larger than primary bounds. - final Rect primaryBounds = getSplitBounds(true /* isPrimary */); - Pair<Size, Size> minDimensionsPair = new Pair<>( - new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + @Test + public void testGetBoundsForPosition_expandContainers() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType()) + .build(); - assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); } @Test - public void testGetBoundsForPosition() { - Activity secondaryActivity = createMockActivity(); - final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); - final Rect primaryBounds = getSplitBounds(true /* isPrimary */); - final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); + public void testGetBoundsForPosition_splitVertically() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + false /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + false /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT) + .build(); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); assertEquals("Primary bounds must be reported.", primaryBounds, - getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) + .build(); + // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE. + taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL; assertEquals("Secondary bounds must be reported.", secondaryBounds, - getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_splitHorizontally() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + true /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + true /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM) + .build(); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) + .build(); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_useHingeFallback() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + false /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + false /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + SplitAttributes.SplitType.RatioSplitType.splitEqually() + )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + + // There's no hinge on the device. Use fallback SplitType. + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + // Hinge is reported, but the host task is in multi-window mode. Still use fallback + // splitType. + doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + taskProperties.getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + // Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match + // folding area orientation. Still use fallback splitType. + doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + taskProperties.getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_fallbackToExpandContainers() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + new SplitAttributes.SplitType.ExpandContainersSplitType() + )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + assertEquals("Task bounds must be reported.", new Rect(), - getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); - Pair<Size, Size> minDimensionsPair = new Pair<>( - new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } - assertEquals("Fullscreen bounds must be reported because of min dimensions.", + @Test + public void testGetBoundsForPosition_useHingeSplitType() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + new SplitAttributes.SplitType.ExpandContainersSplitType() + )).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM) + .build(); + final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo(); + doReturn(windowLayoutInfo).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds(); + final Rect primaryBounds = new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + hingeBounds.top + ); + final Rect secondaryBounds = new Rect( + TASK_BOUNDS.left, + hingeBounds.bottom, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom + ); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", new Rect(), - getBoundsForPosition(POSITION_START, TASK_BOUNDS, - splitRule, mActivity, minDimensionsPair)); + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); } @Test public void testExpandSplitContainerIfNeeded() { - SplitContainer splitContainer = mock(SplitContainer.class); Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); - doReturn(splitRule).when(splitContainer).getSplitRule(); - doReturn(primaryTf).when(splitContainer).getPrimaryContainer(); - doReturn(secondaryTf).when(splitContainer).getSecondaryContainer(); + SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity, + secondaryTf, splitRule, SPLIT_ATTRIBUTES); assertThrows(IllegalArgumentException.class, () -> mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, @@ -224,11 +441,13 @@ public class SplitPresenterTest { splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); secondaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); @@ -238,6 +457,7 @@ public class SplitPresenterTest { verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken()); verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); clearInvocations(mPresenter); assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, @@ -256,6 +476,7 @@ public class SplitPresenterTest { final SplitPairRule rule = new SplitPairRule.Builder(pair -> pair.first == mActivity && pair.second == secondaryActivity, pair -> false, metrics -> true) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .setShouldClearTop(false) .build(); @@ -268,6 +489,49 @@ public class SplitPresenterTest { assertTrue(secondaryTf.isAbove(primaryTf)); } + @Test + public void testComputeSplitAttributes() { + final SplitPairRule splitPairRule = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS)) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + + assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + + final Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null); + + assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, minDimensionsPair)); + + taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect( + TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1, + TASK_BOUNDS.bottom + 1)); + + assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType( + new SplitAttributes.SplitType.HingeSplitType( + SplitAttributes.SplitType.RatioSplitType.splitEqually() + ) + ).build(); + + mController.setSplitAttributesCalculator(params -> { + return splitAttributes; + }); + + assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); @@ -279,4 +543,10 @@ public class SplitPresenterTest { doReturn(mock(IBinder.class)).when(activity).getActivityToken(); return activity; } + + private static TaskContainer.TaskProperties getTaskProperty() { + final Configuration configuration = new Configuration(); + configuration.windowConfiguration.setBounds(TASK_BOUNDS); + return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index dd67e48ef353..af9c6ba5c162 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -21,9 +21,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -34,8 +35,10 @@ import static org.mockito.Mockito.mock; import android.app.Activity; import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentParentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -66,7 +69,7 @@ public class TaskContainerTest { @Test public void testIsTaskBoundsInitialized() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertFalse(taskContainer.isTaskBoundsInitialized()); @@ -77,7 +80,7 @@ public class TaskContainerTest { @Test public void testSetTaskBounds() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertFalse(taskContainer.setTaskBounds(new Rect())); @@ -87,30 +90,24 @@ public class TaskContainerTest { } @Test - public void testIsWindowingModeInitialized() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); - - assertFalse(taskContainer.isWindowingModeInitialized()); - - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - assertTrue(taskContainer.isWindowingModeInitialized()); - } - - @Test public void testGetWindowingModeForSplitTaskFragment() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final Rect splitBounds = new Rect(0, 0, 500, 1000); + final Configuration configuration = new Configuration(); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertEquals(WINDOWING_MODE_FREEFORM, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); @@ -123,22 +120,27 @@ public class TaskContainerTest { @Test public void testIsInPictureInPicture() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); + final Configuration configuration = new Configuration(); assertFalse(taskContainer.isInPictureInPicture()); - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertFalse(taskContainer.isInPictureInPicture()); - taskContainer.setWindowingMode(WINDOWING_MODE_PINNED); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertTrue(taskContainer.isInPictureInPicture()); } @Test public void testIsEmpty() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertTrue(taskContainer.isEmpty()); @@ -155,7 +157,7 @@ public class TaskContainerTest { @Test public void testGetTopTaskFragmentContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopTaskFragmentContainer()); final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, @@ -169,7 +171,7 @@ public class TaskContainerTest { @Test public void testGetTopNonFinishingActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 082774e048a9..35415d816d8b 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -16,8 +16,8 @@ package androidx.window.extensions.embedding; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -90,7 +90,7 @@ public class TaskFragmentContainerTest { @Test public void testNewContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // One of the activity and the intent must be non-null assertThrows(IllegalArgumentException.class, @@ -103,40 +103,39 @@ public class TaskFragmentContainerTest { @Test public void testFinish() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); doReturn(container).when(mController).getContainerWithActivity(mActivity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); // Only remove the activity, but not clear the reference until appeared. - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity).finish(); + verify(mTransaction).finishActivity(mActivity.getActivityToken()); verify(mPresenter, never()).deleteTaskFragment(any(), any()); verify(mController, never()).removeContainer(any()); // Calling twice should not finish activity again. - clearInvocations(mActivity); - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + clearInvocations(mTransaction); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); verify(mPresenter, never()).deleteTaskFragment(any(), any()); verify(mController, never()).removeContainer(any()); // Remove all references after the container has appeared in server. doReturn(new ArrayList<>()).when(mInfo).getActivities(); container.setInfo(mTransaction, mInfo); - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity, never()).finish(); - verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken()); + verify(mTransaction, never()).finishActivity(any()); + verify(mPresenter).deleteTaskFragment(mTransaction, container.getTaskFragmentToken()); verify(mController).removeContainer(container); } @Test public void testFinish_notFinishActivityThatIsReparenting() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); @@ -150,14 +149,14 @@ public class TaskFragmentContainerTest { // The activity is requested to be reparented, so don't finish it. container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken()); verify(mController).removeContainer(container0); } @Test public void testSetInfo() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // Pending activity should be cleared when it has appeared on server side. final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); @@ -185,7 +184,7 @@ public class TaskFragmentContainerTest { @Test public void testIsWaitingActivityAppear() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); @@ -207,7 +206,7 @@ public class TaskFragmentContainerTest { @Test public void testAppearEmptyTimeout() { doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); @@ -247,7 +246,7 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); List<Activity> activities = container.collectNonFinishingActivities(); @@ -275,7 +274,7 @@ public class TaskFragmentContainerTest { @Test public void testAddPendingActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); @@ -289,7 +288,7 @@ public class TaskFragmentContainerTest { @Test public void testIsAbove() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, @@ -301,7 +300,7 @@ public class TaskFragmentContainerTest { @Test public void testGetBottomMostActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); @@ -318,7 +317,7 @@ public class TaskFragmentContainerTest { @Test public void testOnActivityDestroyed() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 40c4c35773f8..36c24c1b6b08 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep ’n ander program in vir verdeelde skerm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index a183a0bf7e85..dff8f3fb2677 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"የካሜራ ችግሮች አሉ?\nዳግም ለማበጀት መታ ያድርጉ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string> <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 98f11dd99957..fa9d2c24dd33 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل هناك مشاكل في الكاميرا؟\nانقر لإعادة الضبط."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string> <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 18cb3ffc61ab..039b7e2dafcc 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো সমস্যা হৈছে নেকি?\nপুনৰ খাপ খোৱাবলৈ টিপক"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্ টানি আনি এৰক"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string> <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 2040288cd726..36229187fa8b 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekrandan istifadə etmək üçün başqa tətbiqi sürüşdürüb gətirin"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index b2b484e9d249..e65268a280b6 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 59db9923f714..31fcc171060b 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Праблемы з камерай?\nНацісніце, каб пераабсталяваць"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 8b5c4a95b1bc..0944d216dd0f 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблеми с камерата?\nДокоснете за ремонтиране"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index d7ff018a2fdf..87eb9ff0e4f7 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ক্যামেরা সংক্রান্ত সমস্যা?\nরিফিট করতে ট্যাপ করুন"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string> <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index f4c456083147..01463c242682 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index c4e6b0d5c80b..c8d0bcc1b2ef 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 8b0d1ff9abde..7012294c0a88 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 94faf4124b7f..e3c99ae5e2f8 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Træk en anden app hertil for at bruge opdelt skærm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 397144594bb9..d231b63109ce 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 023c2d68023b..0a4f88a951c4 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nΠατήστε για επιδιόρθωση."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index b5b6670d0990..042bc8a5bd4d 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra app para el modo de pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index e38fc09d74d4..9234ad224f1b 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra aplicación para activar la pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index f46845265388..ea5005d655b5 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 2c1ee82f34b2..1e5e48558493 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Pantaila zatituta ikusteko, arrastatu beste aplikazio bat"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 6b7bf4d9048b..df43d55745a7 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index b82a7cc4e8f7..a4acec4b6701 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 449dc27f0d70..acc97f88358e 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre application pour utiliser l\'écran partagé"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 15148b71e79d..d063f71347c7 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index b848fd0539e9..2cd8a4a060f2 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra outra aplicación para usar a pantalla dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 79d27a24d758..2ade063ecd3c 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં સમસ્યાઓ છે?\nફરીથી ફિટ કરવા માટે ટૅપ કરો"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string> <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index e803abe08511..0fd83d3065aa 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्या कैमरे से जुड़ी कोई समस्या है?\nफिर से फ़िट करने के लिए टैप करें"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string> <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 07b946d4bd3a..6ea911dc127c 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 08b35bfc4a59..e149f5c73e43 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 9d81e8cb544e..070fb9470db5 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index f74e83f702c9..b5a1de166e82 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 71f6ec77ccd2..4e935a2bc5cd 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dragðu annað forrit inn til að nota skjáskiptingu"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 458313567df3..c4b572185618 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trascina in un\'altra app per usare lo schermo diviso"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index bd52cd827e83..edd2cb6411de 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string> <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 98b3ba61dd7f..721ef6c66a86 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index fbf0b5ec41c4..d4aaba0ca05a 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"კამერად პრობლემები აქვს?\nშეეხეთ გამოსასწორებლად"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string> <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index a75dc3c26a20..a4ff2a93f00b 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 6913c0663f9b..47367f5ea526 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"មានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាឬ?\nចុចដើម្បីដោះស្រាយ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបានដោះស្រាយបញ្ហានេះទេឬ?\nចុចដើម្បីត្រឡប់"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាទេឬ? ចុចដើម្បីច្រានចោល។"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូសកម្មវិធីមួយទៀតចូល ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 1f246d5189fb..001e12298cdf 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿವೆಯೇ?\nಮರುಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string> <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index e878cef5e75a..27e294ebd5d6 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string> <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 20c462e6ad05..d46fb66cff54 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада маселелер келип чыктыбы?\nОңдоо үчүн таптаңыз"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 3f4a881c042b..d7d34d703431 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ?\nແຕະເພື່ອປັບໃໝ່"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອປິດໄວ້."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string> <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 515a263a099f..4b16f63666d0 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 080c6f4682ff..36743cfd171f 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 47ed632fbffe..52a9377af22e 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index faa6a30fdf78..343ccf13427a 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ക്യാമറ പ്രശ്നങ്ങളുണ്ടോ?\nശരിയാക്കാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string> <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 8e8d06db0436..5370ef68dbd4 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерын асуудал гарсан уу?\nДахин тааруулахын тулд товшино уу"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 4fc03b276889..1433ce4b0ab2 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"कॅमेराशी संबंधित काही समस्या आहेत का?\nपुन्हा फिट करण्यासाठी टॅप करा"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्यासाठी टॅप करा."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string> <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 2db225aba9f4..04805dac59fe 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Seret apl lain untuk skrin pisah"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index e8ab4b04b4dc..092cea20fb08 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ကင်မရာပြဿနာလား။\nပြင်ဆင်ရန် တို့ပါ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 278de2d8f1b1..22fa7f2efafc 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra inn en annen app for å bruke delt skjerm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 529f4011b565..9502421e46b6 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्यामेरासम्बन्धी समस्या देखियो?\nसमस्या हल गर्न ट्याप गर्नुहोस्"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 88c220cd7dfe..37fe1fd2b3ed 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep een andere app hier naartoe om het scherm te splitsen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index 2c82fbcf9ff7..ca31f3cf84a7 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"କ୍ୟାମେରାରେ ସମସ୍ୟା ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 8c7229f58730..1f118c901243 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 315b95e88265..4171aebcf99a 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Przeciągnij drugą aplikację, aby podzielić ekran"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 48781ad437bd..7a62410ad125 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index e2be183e0ada..00549026483f 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outra app para usar o ecrã dividido"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 48781ad437bd..7a62410ad125 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 65b0472805cf..352f81b7b6f0 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -17,21 +17,21 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="pip_phone_close" msgid="5783752637260411309">"Închideți"</string> - <string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string> + <string name="pip_phone_close" msgid="5783752637260411309">"Închide"</string> + <string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string> <string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string> - <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string> + <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string> <string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string> <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meniu picture-in-picture"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string> <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string> - <string name="pip_play" msgid="3496151081459417097">"Redați"</string> - <string name="pip_pause" msgid="690688849510295232">"Întrerupeți"</string> + <string name="pip_play" msgid="3496151081459417097">"Redă"</string> + <string name="pip_pause" msgid="690688849510295232">"Întrerupe"</string> <string name="pip_skip_to_next" msgid="8403429188794867653">"Treceți la următorul"</string> <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treceți la cel anterior"</string> <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionați"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stocați"</string> - <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulați stocarea"</string> + <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string> @@ -50,19 +50,19 @@ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Partea de jos pe ecran complet"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Folosirea modului cu o mână"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisați în sus din partea de jos a ecranului sau atingeți oriunde deasupra ferestrei aplicației"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activați modul cu o mână"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activează modul cu o mână"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Părăsiți modul cu o mână"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Setări pentru baloanele <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Suplimentar"</string> - <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adăugați înapoi în stivă"</string> + <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adaugă înapoi în stivă"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>"</string> - <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mutați în stânga sus"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mutați în dreapta sus"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mutați în stânga jos"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mutați în dreapta jos"</string> + <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mută în stânga sus"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mută în dreapta sus"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mută în stânga jos"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> - <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închideți balonul"</string> + <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișați conversația în balon"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atingeți pentru a deschide balonul. Trageți pentru a-l muta."</string> @@ -72,21 +72,18 @@ <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string> - <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</string> + <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string> <string name="restart_button_description" msgid="6712141648865547958">"Atingeți ca să reporniți aplicația pentru o vizualizare mai bună."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Aveți probleme cu camera foto?\nAtingeți pentru a reîncadra"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ați remediat problema?\nAtingeți pentru a reveni"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu aveți probleme cu camera foto? Atingeți pentru a închide."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> - <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extindeți pentru mai multe informații"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximizați"</string> <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string> - <string name="close_button_text" msgid="2913281996024033299">"Închideți"</string> + <string name="close_button_text" msgid="2913281996024033299">"Închide"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 8affb9add8b9..1a77e428ccf3 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблемы с камерой?\nНажмите, чтобы исправить."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index c816065b3585..dc89ec3a836c 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"කැමරා ගැටලුද?\nයළි සවි කිරීමට තට්ටු කරන්න"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්රතිවර්තනය කිරීමට තට්ටු කරන්න"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string> <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 9dfbd54942e3..aec8501fca23 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Rozdelenú obrazovku aktivujete presunutím ďalšie aplikácie"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index 5bf943b96383..44462b6edb04 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 09559990dc37..6e26ec6214ef 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index b42d98e422ff..94725cbdf367 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблема са камером?\nДодирните да бисте поново уклопили"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 162b57dbd224..6b6ba2b2d298 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra till en annan app för läget Delad skärm"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 3844d01f5221..102e9cfae2e4 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Buruta ndani programu nyingine ili utumie hali ya skrini iliyogawanywa"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index c45409c3713c..c2166fdb136a 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string> <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 0a61f937e3b6..ef0f9e7a6cff 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా సమస్యలు ఉన్నాయా?\nరీఫిట్ చేయడానికి ట్యాప్ చేయండి"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్లోకి లాగండి"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string> <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 0a41d450a0b5..7a7575d53035 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"หากพบปัญหากับกล้อง\nแตะเพื่อแก้ไข"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index e2727992777f..1c8d94f24dfa 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Mag-drag ng ibang app para sa split screen"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string> <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 050fa5fee690..82e3f5847df8 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index d5f047fcaf1f..218d11eaba0a 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми з камерою?\nНатисніть, щоб пристосувати"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 700ecaa7cfda..4a9c079fc7d1 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"کیمرے کے مسائل؟\nدوبارہ فٹ کرنے کیلئے تھپتھپائیں"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string> <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index e843b0be5b1e..a063476e1aa8 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index ec1eadbfa988..b47296590324 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Kéo vào một ứng dụng khác để chia đôi màn hình"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string> <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 37d424402f0c..d7366952f155 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相机有问题?\n点按即可整修"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 170cd4cae753..8eda853fbf2b 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題?\n輕按即可修正"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 83f8520b2be4..71f4f2b96346 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題嗎?\n輕觸即可修正"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string> <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 686310ac9881..f637912b8ed9 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -78,12 +78,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string> - <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) --> - <skip /> - <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) --> - <skip /> - <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) --> - <skip /> + <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string> + <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 5696b8dfa8e3..0bc70857a113 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -297,4 +297,28 @@ when the pinned stack size is overridden by app via minWidth/minHeight. --> <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> + + <!-- The size of the drag handle / menu shown along with a floating task. --> + <dimen name="floating_task_menu_size">32dp</dimen> + + <!-- The size of menu items in the floating task menu. --> + <dimen name="floating_task_menu_item_size">24dp</dimen> + + <!-- The horizontal margin of menu items in the floating task menu. --> + <dimen name="floating_task_menu_item_padding">5dp</dimen> + + <!-- The width of visible floating view region when stashed. --> + <dimen name="floating_task_stash_offset">32dp</dimen> + + <!-- The amount of elevation for a floating task. --> + <dimen name="floating_task_elevation">8dp</dimen> + + <!-- The amount of padding around the bottom and top of the task. --> + <dimen name="floating_task_vertical_padding">8dp</dimen> + + <!-- The normal size of the dismiss target. --> + <dimen name="floating_task_dismiss_circle_size">150dp</dimen> + + <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> + <dimen name="floating_dismiss_circle_small">120dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 9230c22c5d95..ca977ed2cb94 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -179,6 +179,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { } /** + * Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}. + */ + @Nullable + public DisplayAreaInfo getDisplayAreaInfo(int displayId) { + return mDisplayAreasInfo.get(displayId); + } + + /** * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by * {@link DisplayAreaInfo#displayId}. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index d88cc007c7b5..1c0e6f726fbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -18,6 +18,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import android.animation.Animator; import android.animation.ValueAnimator; @@ -129,12 +130,20 @@ class ActivityEmbeddingAnimationRunner { @NonNull private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + boolean isChangeTransition = false; for (TransitionInfo.Change change : info.getChanges()) { - if (change.getMode() == TRANSIT_CHANGE + if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { + // Skip the animation if the windows are behind an app starting window. + return new ArrayList<>(); + } + if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { - return createChangeAnimationAdapters(info, startTransaction); + isChangeTransition = true; } } + if (isChangeTransition) { + return createChangeAnimationAdapters(info, startTransaction); + } if (Transitions.isClosingType(info.getType())) { return createCloseAnimationAdapters(info); } @@ -276,7 +285,8 @@ class ActivityEmbeddingAnimationRunner { } final Animation animation; - if (!TransitionInfo.isIndependent(change, info)) { + if (change.getParent() != null + && handledChanges.contains(info.getChange(change.getParent()))) { // No-op if it will be covered by the changing parent window. animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (Transitions.isClosingType(change.getMode())) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index b5a575499a3a..922472a26113 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -59,6 +59,8 @@ import java.util.concurrent.Executor; public class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; + public static final String KEY_APP_BUBBLE = "key_app_bubble"; + private final String mKey; @Nullable private final String mGroupKey; @@ -164,6 +166,14 @@ public class Bubble implements BubbleViewProvider { private PendingIntent mDeleteIntent; /** + * Used only for a special bubble in the stack that has the key {@link #KEY_APP_BUBBLE}. + * There can only be one of these bubbles in the stack and this intent will be populated for + * that bubble. + */ + @Nullable + private Intent mAppIntent; + + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ @@ -192,6 +202,22 @@ public class Bubble implements BubbleViewProvider { mBubbleMetadataFlagListener = listener; } + public Bubble(Intent intent, + UserHandle user, + Executor mainExecutor) { + mKey = KEY_APP_BUBBLE; + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = user; + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mTaskId = INVALID_TASK_ID; + mAppIntent = intent; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = intent.getPackage(); + } + @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, @@ -417,6 +443,9 @@ public class Bubble implements BubbleViewProvider { mShortcutInfo = info.shortcutInfo; mAppName = info.appName; + if (mTitle == null) { + mTitle = mAppName; + } mFlyoutMessage = info.flyoutMessage; mBadgeBitmap = info.badgeBitmap; @@ -520,7 +549,7 @@ public class Bubble implements BubbleViewProvider { * @return the last time this bubble was updated or accessed, whichever is most recent. */ long getLastActivity() { - return Math.max(mLastUpdated, mLastAccessed); + return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed); } /** @@ -719,6 +748,15 @@ public class Bubble implements BubbleViewProvider { return mDeleteIntent; } + @Nullable + Intent getAppBubbleIntent() { + return mAppIntent; + } + + boolean isAppBubble() { + return KEY_APP_BUBBLE.equals(mKey); + } + Intent getSettingsIntent(final Context context) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 0dfba3464f23..93413dbe7e5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1017,6 +1017,20 @@ public class BubbleController implements ConfigurationChangeListener { } /** + * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification + * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble + * is supported at a time. + * + * @param intent the intent to display in the bubble expanded view. + */ + public void addAppBubble(Intent intent) { + if (intent == null || intent.getPackage() == null) return; + Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor); + b.setShouldAutoExpand(true); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + + /** * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 54c91dde7668..8121b206c93a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -224,15 +224,23 @@ public class BubbleExpandedView extends LinearLayout { try { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); - if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { + + Intent fillInIntent = new Intent(); + // Apply flags to make behaviour match documentLaunchMode=always. + fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + + if (mBubble.isAppBubble()) { + PendingIntent pi = PendingIntent.getActivity(mContext, 0, + mBubble.getAppBubbleIntent(), + PendingIntent.FLAG_MUTABLE, + null); + mTaskView.startActivity(pi, fillInIntent, options, launchBounds); + } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); if (mBubble != null) { mBubble.setIntentActive(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java index 976fba52b9e2..e0c782d1675b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java @@ -49,6 +49,8 @@ public class DismissCircleView extends FrameLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + final Resources res = getResources(); + setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); setViewSizes(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 7c3c14e2210c..33074de8f583 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; +import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; @@ -60,6 +61,8 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.floating.FloatingTasks; +import com.android.wm.shell.floating.FloatingTasksController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; @@ -572,6 +575,47 @@ public abstract class WMShellBaseModule { } // + // Floating tasks + // + + @WMSingleton + @Provides + static Optional<FloatingTasks> provideFloatingTasks( + Optional<FloatingTasksController> floatingTaskController) { + return floatingTaskController.map((controller) -> controller.asFloatingTasks()); + } + + @WMSingleton + @Provides + static Optional<FloatingTasksController> provideFloatingTasksController(Context context, + ShellInit shellInit, + ShellController shellController, + ShellCommandHandler shellCommandHandler, + Optional<BubbleController> bubbleController, + WindowManager windowManager, + ShellTaskOrganizer organizer, + TaskViewTransitions taskViewTransitions, + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor, + SyncTransactionQueue syncQueue) { + if (FloatingTasksController.FLOATING_TASKS_ENABLED) { + return Optional.of(new FloatingTasksController(context, + shellInit, + shellController, + shellCommandHandler, + bubbleController, + windowManager, + organizer, + taskViewTransitions, + mainExecutor, + bgExecutor, + syncQueue)); + } else { + return Optional.empty(); + } + } + + // // Starting window // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 27d3e35fa07a..35e88e9abb3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -27,7 +27,6 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; -import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewTransitions; @@ -599,13 +598,13 @@ public abstract class WMShellModule { static Optional<DesktopModeController> provideDesktopModeController( Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - RootDisplayAreaOrganizer rootDisplayAreaOrganizer, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @ShellMainThread Handler mainHandler, Transitions transitions ) { if (DesktopMode.IS_SUPPORTED) { return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer, - rootDisplayAreaOrganizer, mainHandler, transitions)); + rootTaskDisplayAreaOrganizer, mainHandler, transitions)); } else { return Optional.empty(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index c07ce1065302..6e44d58cffae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -22,19 +22,21 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; +import android.app.WindowConfiguration; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; +import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.RootDisplayAreaOrganizer; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; @@ -47,18 +49,18 @@ public class DesktopModeController { private final Context mContext; private final ShellTaskOrganizer mShellTaskOrganizer; - private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final SettingsObserver mSettingsObserver; private final Transitions mTransitions; public DesktopModeController(Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - RootDisplayAreaOrganizer rootDisplayAreaOrganizer, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @ShellMainThread Handler mainHandler, Transitions transitions) { mContext = context; mShellTaskOrganizer = shellTaskOrganizer; - mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSettingsObserver = new SettingsObserver(mContext, mainHandler); mTransitions = transitions; shellInit.addInitCallback(this::onInit, this); @@ -92,15 +94,32 @@ public class DesktopModeController { wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId), true /* transfer */); } - wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId, - targetWindowingMode), true /* transfer */); + prepareWindowingModeChange(wct, displayId, targetWindowingMode); if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.startTransition(TRANSIT_CHANGE, wct, null); } else { - mRootDisplayAreaOrganizer.applyTransaction(wct); + mRootTaskDisplayAreaOrganizer.applyTransaction(wct); } } + private void prepareWindowingModeChange(WindowContainerTransaction wct, + int displayId, @WindowConfiguration.WindowingMode int windowingMode) { + DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer + .getDisplayAreaInfo(displayId); + if (displayAreaInfo == null) { + ProtoLog.e(WM_SHELL_DESKTOP_MODE, + "unable to update windowing mode for display %d display not found", displayId); + return; + } + + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId, + displayAreaInfo.configuration.windowConfiguration.getWindowingMode(), + windowingMode); + + wct.setWindowingMode(displayAreaInfo.token, windowingMode); + } + /** * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE} */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java new file mode 100644 index 000000000000..83a1734dc71a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.DismissView; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.floating.views.FloatingTaskLayer; +import com.android.wm.shell.floating.views.FloatingTaskView; + +import java.util.Objects; + +/** + * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved + * close to the target to be stuck to it unless moved out again. + */ +public class FloatingDismissController { + + /** Velocity required to dismiss the view without dragging it into the dismiss target. */ + private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f; + /** + * Max velocity that the view can be moving through the target with to stick (i.e. if it's + * more than this velocity, it will pass through the target. + */ + private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f; + /** + * Percentage of the target width to use to determine if an object flung towards the target + * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a + * 200px-wide area around the target will be considered 'near' enough get dismissed). + */ + private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f; + /** Minimum alpha to apply to the view being dismissed when it is in the target. */ + private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f; + /** Amount to scale down the view being dismissed when it is in the target. */ + private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f; + + private Context mContext; + private FloatingTasksController mController; + private FloatingTaskLayer mParent; + + private DismissView mDismissView; + private ValueAnimator mDismissAnimator; + private View mViewBeingDismissed; + private float mDismissSizePercent; + private float mDismissSize; + + /** + * The currently magnetized object, which is being dragged and will be attracted to the magnetic + * dismiss target. + */ + private MagnetizedObject<View> mMagnetizedObject; + /** + * The MagneticTarget instance for our circular dismiss view. This is added to the + * MagnetizedObject instances for the view being dragged. + */ + private MagnetizedObject.MagneticTarget mMagneticTarget; + /** Magnet listener that handles animating and dismissing the view. */ + private MagnetizedObject.MagnetListener mFloatingViewMagnetListener; + + public FloatingDismissController(Context context, FloatingTasksController controller, + FloatingTaskLayer parent) { + mContext = context; + mController = controller; + mParent = parent; + updateSizes(); + createAndAddDismissView(); + + mDismissAnimator = ValueAnimator.ofFloat(1f, 0f); + mDismissAnimator.addUpdateListener(animation -> { + final float value = (float) animation.getAnimatedValue(); + if (mDismissView != null) { + mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f); + mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f); + final float scaleValue = Math.max(value, mDismissSizePercent); + mDismissView.getCircle().setScaleX(scaleValue); + mDismissView.getCircle().setScaleY(scaleValue); + } + if (mViewBeingDismissed != null) { + // TODO: alpha doesn't actually apply to taskView currently. + mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA)); + mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); + mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); + } + }); + + mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget( + @NonNull MagnetizedObject.MagneticTarget target) { + animateDismissing(/* dismissing= */ true); + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + animateDismissing(/* dismissing= */ false); + mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY, + wasFlungOut); + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + doDismiss(); + } + }; + } + + /** Updates all the sizes used and applies them to the {@link DismissView}. */ + public void updateSizes() { + Resources res = mContext.getResources(); + mDismissSize = res.getDimensionPixelSize( + R.dimen.floating_task_dismiss_circle_size); + final float minDismissSize = res.getDimensionPixelSize( + R.dimen.floating_dismiss_circle_small); + mDismissSizePercent = minDismissSize / mDismissSize; + + if (mDismissView != null) { + mDismissView.updateResources(); + } + } + + /** Prepares the view being dragged to be magnetic. */ + public void setUpMagneticObject(View viewBeingDragged) { + mViewBeingDismissed = viewBeingDragged; + mMagnetizedObject = getMagnetizedView(viewBeingDragged); + mMagnetizedObject.clearAllTargets(); + mMagnetizedObject.addTarget(mMagneticTarget); + mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener); + } + + /** Shows or hides the dismiss target. */ + public void showDismiss(boolean show) { + if (show) { + mDismissView.show(); + } else { + mDismissView.hide(); + } + } + + /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ + public boolean passEventToMagnetizedObject(MotionEvent event) { + return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); + } + + private void createAndAddDismissView() { + if (mDismissView != null) { + mParent.removeView(mDismissView); + } + mDismissView = new DismissView(mContext); + mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size); + mDismissView.updateResources(); + mParent.addView(mDismissView); + + final float dismissRadius = mDismissSize; + // Save the MagneticTarget instance for the newly set up view - we'll add this to the + // MagnetizedObjects when the dismiss view gets shown. + mMagneticTarget = new MagnetizedObject.MagneticTarget( + mDismissView.getCircle(), (int) dismissRadius); + } + + private MagnetizedObject<View> getMagnetizedView(View v) { + if (mMagnetizedObject != null + && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) { + // Same view being dragged, we can reuse the magnetic object. + return mMagnetizedObject; + } + MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>( + mContext, + v, + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y + ) { + @Override + public float getWidth(@NonNull View underlyingObject) { + return underlyingObject.getWidth(); + } + + @Override + public float getHeight(@NonNull View underlyingObject) { + return underlyingObject.getHeight(); + } + + @Override + public void getLocationOnScreen(@NonNull View underlyingObject, + @NonNull int[] loc) { + loc[0] = (int) underlyingObject.getTranslationX(); + loc[1] = (int) underlyingObject.getTranslationY(); + } + }; + magnetizedView.setHapticsEnabled(true); + magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY); + magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT); + return magnetizedView; + } + + /** Animates the dismiss treatment on the view being dismissed. */ + private void animateDismissing(boolean shouldDismiss) { + if (mViewBeingDismissed == null) { + return; + } + if (shouldDismiss) { + mDismissAnimator.removeAllListeners(); + mDismissAnimator.start(); + } else { + mDismissAnimator.removeAllListeners(); + mDismissAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + resetDismissAnimator(); + } + }); + mDismissAnimator.reverse(); + } + } + + /** Actually dismisses the view. */ + private void doDismiss() { + mDismissView.hide(); + mController.removeTask(); + resetDismissAnimator(); + mViewBeingDismissed = null; + } + + private void resetDismissAnimator() { + mDismissAnimator.removeAllListeners(); + mDismissAnimator.cancel(); + if (mDismissView != null) { + mDismissView.cancelAnimators(); + mDismissView.getCircle().setScaleX(1f); + mDismissView.getCircle().setScaleY(1f); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java new file mode 100644 index 000000000000..935666026bf4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating; + +import android.content.Intent; + +import com.android.wm.shell.common.annotations.ExternalThread; + +/** + * Interface to interact with floating tasks. + */ +@ExternalThread +public interface FloatingTasks { + + /** + * Shows, stashes, or un-stashes the floating task depending on state: + * - If there is no floating task for this intent, it shows the task for the provided intent. + * - If there is a floating task for this intent, but it's stashed, this un-stashes it. + * - If there is a floating task for this intent, and it's not stashed, this stashes it. + */ + void showOrSetStashed(Intent intent); + + /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */ + default IFloatingTasks createExternalInterface() { + return null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java new file mode 100644 index 000000000000..67552991869b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.SystemProperties; +import android.view.ViewGroup; +import android.view.WindowManager; + +import androidx.annotation.BinderThread; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; +import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.annotations.ShellBackgroundThread; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.floating.views.FloatingTaskLayer; +import com.android.wm.shell.floating.views.FloatingTaskView; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; + +import java.io.PrintWriter; +import java.util.Objects; +import java.util.Optional; + +/** + * Entry point for creating and managing floating tasks. + * + * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView} + * within that window. + * + * Currently optimized for a single task. Multiple tasks are not supported. + */ +public class FloatingTasksController implements RemoteCallable<FloatingTasksController>, + ConfigurationChangeListener { + + private static final String TAG = FloatingTasksController.class.getSimpleName(); + + public static final boolean FLOATING_TASKS_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); + public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES = + SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false); + + @VisibleForTesting + static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600; + + // Only used for testing + private Configuration mConfig; + private boolean mFloatingTasksEnabledForTests; + + private FloatingTaskImpl mImpl = new FloatingTaskImpl(); + private Context mContext; + private ShellController mShellController; + private ShellCommandHandler mShellCommandHandler; + private @Nullable BubbleController mBubbleController; + private WindowManager mWindowManager; + private ShellTaskOrganizer mTaskOrganizer; + private TaskViewTransitions mTaskViewTransitions; + private @ShellMainThread ShellExecutor mMainExecutor; + // TODO: mBackgroundThread is not used but we'll probs need it eventually? + private @ShellBackgroundThread ShellExecutor mBackgroundThread; + private SyncTransactionQueue mSyncQueue; + + private boolean mIsFloatingLayerAdded; + private FloatingTaskLayer mFloatingTaskLayer; + private final Point mLastPosition = new Point(-1, -1); + + private Task mTask; + + // Simple class to hold onto info for intent or shortcut based tasks. + public static class Task { + public int taskId = INVALID_TASK_ID; + @Nullable + public Intent intent; + @Nullable + public ShortcutInfo info; + @Nullable + public FloatingTaskView floatingView; + } + + public FloatingTasksController(Context context, + ShellInit shellInit, + ShellController shellController, + ShellCommandHandler shellCommandHandler, + Optional<BubbleController> bubbleController, + WindowManager windowManager, + ShellTaskOrganizer organizer, + TaskViewTransitions transitions, + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExceutor, + SyncTransactionQueue syncTransactionQueue) { + mContext = context; + mShellController = shellController; + mShellCommandHandler = shellCommandHandler; + mBubbleController = bubbleController.get(); + mWindowManager = windowManager; + mTaskOrganizer = organizer; + mTaskViewTransitions = transitions; + mMainExecutor = mainExecutor; + mBackgroundThread = bgExceutor; + mSyncQueue = syncTransactionQueue; + if (isFloatingTasksEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } + mShellCommandHandler.addDumpCallback(this::dump, this); + } + + protected void onInit() { + mShellController.addConfigurationChangeListener(this); + } + + /** Only used for testing. */ + @VisibleForTesting + void setConfig(Configuration config) { + mConfig = config; + } + + /** Only used for testing. */ + @VisibleForTesting + void setFloatingTasksEnabled(boolean enabled) { + mFloatingTasksEnabledForTests = enabled; + } + + /** Whether the floating layer is available. */ + boolean isFloatingLayerAvailable() { + Configuration config = mConfig == null + ? mContext.getResources().getConfiguration() + : mConfig; + return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; + } + + /** Whether floating tasks are enabled. */ + boolean isFloatingTasksEnabled() { + return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests; + } + + @Override + public void onThemeChanged() { + if (mIsFloatingLayerAdded) { + mFloatingTaskLayer.updateSizes(); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // TODO: probably other stuff here to do (e.g. handle rotation) + if (mIsFloatingLayerAdded) { + mFloatingTaskLayer.updateSizes(); + } + } + + /** Returns false if the task shouldn't be shown. */ + private boolean canShowTask(Intent intent) { + ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask -- %s", intent); + if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false; + if (intent == null) { + ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing"); + return false; + } + return true; + } + + /** Returns true if the task was or should be shown as a bubble. */ + private boolean maybeShowTaskAsBubble(Intent intent) { + if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) { + removeFloatingLayer(); + if (intent.getPackage() != null) { + mBubbleController.addAppBubble(intent); + ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent); + } else { + ProtoLog.d(WM_SHELL_FLOATING_APPS, + "failed to show floating task as bubble: %s; unknown package", intent); + } + return true; + } + return false; + } + + /** + * Shows, stashes, or un-stashes the floating task depending on state: + * - If there is no floating task for this intent, it shows this the provided task. + * - If there is a floating task for this intent, but it's stashed, this un-stashes it. + * - If there is a floating task for this intent, and it's not stashed, this stashes it. + */ + public void showOrSetStashed(Intent intent) { + if (!canShowTask(intent)) return; + if (maybeShowTaskAsBubble(intent)) return; + + addFloatingLayer(); + + if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { + // The task is already added, toggle the stash state. + mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed()); + return; + } + + // If we're here it's either a new or different task + showNewTask(intent); + } + + /** + * Shows a floating task with the provided intent. + * If the same task is present it will un-stash it or do nothing if it is already un-stashed. + * Removes any other floating tasks that might exist. + */ + public void showTask(Intent intent) { + if (!canShowTask(intent)) return; + if (maybeShowTaskAsBubble(intent)) return; + + addFloatingLayer(); + + if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { + // The task is already added, show it if it's stashed. + if (mTask.floatingView.isStashed()) { + mFloatingTaskLayer.setStashed(mTask, false); + } + return; + } + showNewTask(intent); + } + + private void showNewTask(Intent intent) { + if (mTask != null && !intent.filterEquals(mTask.intent)) { + mFloatingTaskLayer.removeAllTaskViews(); + mTask.floatingView.cleanUpTaskView(); + mTask = null; + } + + FloatingTaskView ftv = new FloatingTaskView(mContext, this); + ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); + + mTask = new Task(); + mTask.floatingView = ftv; + mTask.intent = intent; + + // Add & start the task. + mFloatingTaskLayer.addTask(mTask); + ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent); + mTask.floatingView.startTask(mMainExecutor, mTask); + } + + /** + * Removes the task and cleans up the view. + */ + public void removeTask() { + if (mTask != null) { + ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId); + + if (mTask.floatingView != null) { + // TODO: animate it + mFloatingTaskLayer.removeView(mTask.floatingView); + mTask.floatingView.cleanUpTaskView(); + } + removeFloatingLayer(); + } + } + + /** + * Whether there is a floating task and if it is stashed. + */ + public boolean isStashed() { + return isTaskAttached(mTask) && mTask.floatingView.isStashed(); + } + + /** + * If a floating task exists, this sets whether it is stashed and animates if needed. + */ + public void setStashed(boolean shouldStash) { + if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) { + mFloatingTaskLayer.setStashed(mTask, shouldStash); + } + } + + /** + * Saves the last position the floating task was in so that it can be put there again. + */ + public void setLastPosition(int x, int y) { + mLastPosition.set(x, y); + } + + /** + * Returns the last position the floating task was in. + */ + public Point getLastPosition() { + return mLastPosition; + } + + /** + * Whether the provided task has a view that's attached to the floating layer. + */ + private boolean isTaskAttached(Task t) { + return t != null && t.floatingView != null + && mIsFloatingLayerAdded + && mFloatingTaskLayer.getTaskViewCount() > 0 + && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView); + } + + // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this + // type allowed? Bubbles & floating tasks should probably be in the same layer to reduce + // # of windows. + private void addFloatingLayer() { + if (mIsFloatingLayerAdded) { + return; + } + + mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager); + + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT + ); + params.setTrustedOverlay(); + params.setFitInsetsTypes(0); + params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + params.setTitle("FloatingTaskLayer"); + params.packageName = mContext.getPackageName(); + params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + + try { + mIsFloatingLayerAdded = true; + mWindowManager.addView(mFloatingTaskLayer, params); + } catch (IllegalStateException e) { + // This means the floating layer has already been added which shouldn't happen. + e.printStackTrace(); + } + } + + private void removeFloatingLayer() { + if (!mIsFloatingLayerAdded) { + return; + } + try { + mIsFloatingLayerAdded = false; + if (mFloatingTaskLayer != null) { + mWindowManager.removeView(mFloatingTaskLayer); + } + } catch (IllegalArgumentException e) { + // This means the floating layer has already been removed which shouldn't happen. + e.printStackTrace(); + } + } + + /** + * Description of current floating task state. + */ + private void dump(PrintWriter pw, String prefix) { + pw.println("FloatingTaskController state:"); + pw.print(" isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable()); + pw.print(" isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled()); + pw.print(" mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded); + pw.print(" mLastPosition= "); pw.println(mLastPosition); + pw.println(); + } + + /** Returns the {@link FloatingTasks} implementation. */ + public FloatingTasks asFloatingTasks() { + return mImpl; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + /** + * The interface for calls from outside the shell, within the host process. + */ + @ExternalThread + private class FloatingTaskImpl implements FloatingTasks { + private IFloatingTasksImpl mIFloatingTasks; + + @Override + public void showOrSetStashed(Intent intent) { + mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent)); + } + + @Override + public IFloatingTasks createExternalInterface() { + if (mIFloatingTasks != null) { + mIFloatingTasks.invalidate(); + } + mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this); + return mIFloatingTasks; + } + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IFloatingTasksImpl extends IFloatingTasks.Stub { + private FloatingTasksController mController; + + IFloatingTasksImpl(FloatingTasksController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + public void showTask(Intent intent) { + executeRemoteCallWithTaskPermission(mController, "showTask", + (controller) -> controller.showTask(intent)); + } + } +} diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl index 104bf99f1537..f79ca1039865 100644 --- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl @@ -14,15 +14,15 @@ * limitations under the License. */ -package android.service.cloudsearch; +package com.android.wm.shell.floating; -import android.app.cloudsearch.SearchRequest; +import android.content.Intent; /** - * Interface from the system to CloudSearch service. - * - * @hide + * Interface that is exposed to remote callers to manipulate floating task features. */ -oneway interface ICloudSearchService { - void onSearch(in SearchRequest request); +interface IFloatingTasks { + + void showTask(in Intent intent) = 1; + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java new file mode 100644 index 000000000000..c922109751ba --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating.views; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.android.wm.shell.R; + +/** + * Displays the menu items for a floating task view (e.g. close). + */ +public class FloatingMenuView extends LinearLayout { + + private int mItemSize; + private int mItemMargin; + + public FloatingMenuView(Context context) { + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER); + + mItemSize = context.getResources().getDimensionPixelSize( + R.dimen.floating_task_menu_item_size); + mItemMargin = context.getResources().getDimensionPixelSize( + R.dimen.floating_task_menu_item_padding); + } + + /** Adds a clickable item to the menu bar. Items are ordered as added. */ + public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) { + ImageView itemView = new ImageView(getContext()); + itemView.setScaleType(ImageView.ScaleType.CENTER); + if (drawable != null) { + itemView.setImageDrawable(drawable); + } + LinearLayout.LayoutParams lp = new LayoutParams(mItemSize, + ViewGroup.LayoutParams.MATCH_PARENT); + lp.setMarginStart(mItemMargin); + lp.setMarginEnd(mItemMargin); + addView(itemView, lp); + + itemView.setOnClickListener(listener); + } + + /** + * The menu extends past the top of the TaskView because of the rounded corners. This means + * to center content in the menu we must subtract the radius (i.e. the amount of space covered + * by TaskView). + */ + public void setCornerRadius(float radius) { + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java new file mode 100644 index 000000000000..16dab2415bf2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating.views; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Region; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.floating.FloatingDismissController; +import com.android.wm.shell.floating.FloatingTasksController; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and + * movement of the task views. + */ +public class FloatingTaskLayer extends FrameLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { + + private static final String TAG = FloatingTaskLayer.class.getSimpleName(); + + /** How big to make the task view based on screen width of the largest size. */ + private static final float START_SIZE_WIDTH_PERCENT = 0.33f; + /** Min fling velocity required to move the view from one side of the screen to the other. */ + private static final float ESCAPE_VELOCITY = 750f; + /** Amount of friction to apply to fling animations. */ + private static final float FLING_FRICTION = 1.9f; + + private final FloatingTasksController mController; + private final FloatingDismissController mDismissController; + private final WindowManager mWindowManager; + private final TouchHandlerImpl mTouchHandler; + + private final Region mTouchableRegion = new Region(); + private final Rect mPositionRect = new Rect(); + private final Point mDefaultStartPosition = new Point(); + private final Point mTaskViewSize = new Point(); + private WindowInsets mWindowInsets; + private int mVerticalPadding; + private int mOverhangWhenStashed; + + private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect()); + private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener = + this::updateSystemGestureExclusion; + + /** Interface allowing something to handle the touch events going to a task. */ + interface FloatingTaskTouchHandler { + void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, + float viewInitialX, float viewInitialY); + + void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, + float dx, float dy); + + void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, + float dx, float dy, float velX, float velY); + + void onClick(@NonNull FloatingTaskView v); + } + + public FloatingTaskLayer(Context context, + FloatingTasksController controller, + WindowManager windowManager) { + super(context); + // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly. + setBackgroundColor(Color.argb(0, 0, 0, 0)); + + mController = controller; + mWindowManager = windowManager; + updateSizes(); + + // TODO: Might make sense to put dismiss controller in the touch handler since that's the + // main user of dismiss controller. + mDismissController = new FloatingDismissController(context, mController, this); + mTouchHandler = new TouchHandlerImpl(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener); + setOnApplyWindowInsetsListener((view, windowInsets) -> { + if (!windowInsets.equals(mWindowInsets)) { + mWindowInsets = windowInsets; + updateSizes(); + } + return windowInsets; + }); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener); + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + mTouchableRegion.setEmpty(); + getTouchableRegion(mTouchableRegion); + inoutInfo.touchableRegion.set(mTouchableRegion); + } + + /** Adds a floating task to the layout. */ + public void addTask(FloatingTasksController.Task task) { + if (task.floatingView == null) return; + + task.floatingView.setTouchHandler(mTouchHandler); + addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y)); + updateTaskViewPosition(task.floatingView); + } + + /** Animates the stashed state of the provided task, if it's part of the floating layer. */ + public void setStashed(FloatingTasksController.Task task, boolean shouldStash) { + if (task.floatingView != null && task.floatingView.getParent() == this) { + mTouchHandler.stashTaskView(task.floatingView, shouldStash); + } + } + + /** Removes all {@link FloatingTaskView} from the layout. */ + public void removeAllTaskViews() { + int childCount = getChildCount(); + ArrayList<View> viewsToRemove = new ArrayList<>(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i) instanceof FloatingTaskView) { + viewsToRemove.add(getChildAt(i)); + } + } + for (View v : viewsToRemove) { + removeView(v); + } + } + + /** Returns the number of task views in the layout. */ + public int getTaskViewCount() { + int taskViewCount = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i) instanceof FloatingTaskView) { + taskViewCount++; + } + } + return taskViewCount; + } + + /** + * Called when the task view is un-stuck from the dismiss target. + * @param v the task view being moved. + * @param velX the x velocity of the motion event. + * @param velY the y velocity of the motion event. + * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there + * was an 'up' event), otherwise the user is still dragging. + */ + public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, + boolean wasFlungOut) { + mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut); + } + + /** + * Updates dimensions and applies them to any task views. + */ + public void updateSizes() { + if (mDismissController != null) { + mDismissController.updateSizes(); + } + + mOverhangWhenStashed = getResources().getDimensionPixelSize( + R.dimen.floating_task_stash_offset); + mVerticalPadding = getResources().getDimensionPixelSize( + R.dimen.floating_task_vertical_padding); + + WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + WindowInsets windowInsets = windowMetrics.getWindowInsets(); + Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() + | WindowInsets.Type.statusBars() + | WindowInsets.Type.displayCutout()); + Rect bounds = windowMetrics.getBounds(); + mPositionRect.set(bounds.left + insets.left, + bounds.top + insets.top + mVerticalPadding, + bounds.right - insets.right, + bounds.bottom - insets.bottom - mVerticalPadding); + + int taskViewWidth = Math.max(bounds.height(), bounds.width()); + int taskViewHeight = Math.min(bounds.height(), bounds.width()); + taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2)); + mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight); + mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top); + + // Update existing views + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i) instanceof FloatingTaskView) { + FloatingTaskView child = (FloatingTaskView) getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.width = mTaskViewSize.x; + lp.height = mTaskViewSize.y; + child.setLayoutParams(lp); + updateTaskViewPosition(child); + } + } + } + + /** Returns the first floating task view in the layout. (Currently only ever 1 view). */ + @Nullable + public FloatingTaskView getFirstTaskView() { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof FloatingTaskView) { + return (FloatingTaskView) child; + } + } + return null; + } + + private void updateTaskViewPosition(FloatingTaskView floatingView) { + Point lastPosition = mController.getLastPosition(); + if (lastPosition.x == -1 && lastPosition.y == -1) { + floatingView.setX(mDefaultStartPosition.x); + floatingView.setY(mDefaultStartPosition.y); + } else { + floatingView.setX(lastPosition.x); + floatingView.setY(lastPosition.y); + } + if (mTouchHandler.isStashedPosition(floatingView)) { + floatingView.setStashed(true); + } + floatingView.updateLocation(); + } + + /** + * Updates the area of the screen that shouldn't allow the back gesture due to the placement + * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would + * un-stash the task view instead of performing the back gesture). + */ + private void updateSystemGestureExclusion() { + Rect excludeZone = mSystemGestureExclusionRects.get(0); + FloatingTaskView floatingTaskView = getFirstTaskView(); + if (floatingTaskView != null && floatingTaskView.isStashed()) { + excludeZone.set(floatingTaskView.getLeft(), + floatingTaskView.getTop(), + floatingTaskView.getRight(), + floatingTaskView.getBottom()); + excludeZone.offset((int) (floatingTaskView.getTranslationX()), + (int) (floatingTaskView.getTranslationY())); + setSystemGestureExclusionRects(mSystemGestureExclusionRects); + } else { + excludeZone.setEmpty(); + setSystemGestureExclusionRects(Collections.emptyList()); + } + } + + /** + * Fills in the touchable region for floating windows. This is used by WindowManager to + * decide which touch events go to the floating windows. + */ + private void getTouchableRegion(Region outRegion) { + int childCount = getChildCount(); + Rect temp = new Rect(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof FloatingTaskView) { + child.getBoundsOnScreen(temp); + outRegion.op(temp, Region.Op.UNION); + } + } + } + + /** + * Implementation of the touch handler. Animates the task view based on touch events. + */ + private class TouchHandlerImpl implements FloatingTaskTouchHandler { + /** + * The view can be stashed by swiping it towards the current edge or moving it there. If + * the view gets moved in a way that is not one of these gestures, this is flipped to false. + */ + private boolean mCanStash = true; + /** + * This is used to indicate that the view has been un-stuck from the dismiss target and + * needs to spring to the current touch location. + */ + // TODO: implement this behavior + private boolean mSpringToTouchOnNextMotionEvent = false; + + private ArrayList<FlingAnimation> mFlingAnimations; + private ViewPropertyAnimator mViewPropertyAnimation; + + private float mViewInitialX; + private float mViewInitialY; + + private float[] mMinMax = new float[2]; + + @Override + public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY) { + mCanStash = true; + mViewInitialX = viewInitialX; + mViewInitialY = viewInitialY; + mDismissController.setUpMagneticObject(v); + mDismissController.passEventToMagnetizedObject(ev); + } + + @Override + public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, + float dx, float dy) { + // Shows the magnetic dismiss target if needed. + mDismissController.showDismiss(/* show= */ true); + + // Send it to magnetic target first. + if (mDismissController.passEventToMagnetizedObject(ev)) { + v.setStashed(false); + mCanStash = true; + + return; + } + + // If we're here magnetic target didn't want it so move as per normal. + + v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true)); + v.setTranslationY(capY(v, mViewInitialY + dy)); + if (v.isStashed()) { + // Check if we've moved far enough to be not stashed. + final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); + final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX; + if (viewInitiallyOnLeftSide) { + if (v.getTranslationX() > mPositionRect.left) { + v.setStashed(false); + mCanStash = true; + } + } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) { + v.setStashed(false); + mCanStash = true; + } + } + } + + // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge. + // TODO clean up the code here, pretty hard to comprehend + // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges) + @Override + public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, + float dx, float dy, float velX, float velY) { + + // Send it to magnetic target first. + if (mDismissController.passEventToMagnetizedObject(ev)) { + v.setStashed(false); + return; + } + mDismissController.showDismiss(/* show= */ false); + + // If we're here magnetic target didn't want it so handle up as per normal. + + final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false); + final float centerX = mPositionRect.centerX(); + final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX; + final boolean viewOnLeftSide = x + v.getWidth() < centerX; + final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY; + final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY; + // TODO: check velX here sometimes it doesn't stash on move when I think it should + final boolean shouldStashFromMove = + (velX < 0 && v.getTranslationX() < mPositionRect.left) + || (velX > 0 + && v.getTranslationX() + v.getWidth() > mPositionRect.right); + final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide + && isFling + && ((viewOnLeftSide && velX < ESCAPE_VELOCITY) + || (!viewOnLeftSide && velX > ESCAPE_VELOCITY)); + final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove); + + ProtoLog.d(WM_SHELL_FLOATING_APPS, + "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s" + + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f" + + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove, + viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed()); + + if (v.isStashed()) { + mMinMax[0] = viewOnLeftSide + ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed + : mPositionRect.right - v.getWidth(); + mMinMax[1] = viewOnLeftSide + ? mPositionRect.left + : mPositionRect.right - mOverhangWhenStashed; + } else { + populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax); + } + + boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide; + float destinationRelativeX = movingLeft + ? mMinMax[0] + : mMinMax[1]; + + // TODO: why is this necessary / when does this happen? + if (mMinMax[1] < v.getTranslationX()) { + mMinMax[1] = v.getTranslationX(); + } + if (v.getTranslationX() < mMinMax[0]) { + mMinMax[0] = v.getTranslationX(); + } + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity + // so that it'll make it all the way to the side of the screen. + final float minimumVelocityToReachEdge = + getMinimumVelocityToReachEdge(v, destinationRelativeX); + final float startXVelocity = movingLeft + ? Math.min(minimumVelocityToReachEdge, velX) + : Math.max(minimumVelocityToReachEdge, velX); + + cancelAnyAnimations(v); + + mFlingAnimations = getAnimationForUpEvent(v, shouldStash, + startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX); + for (int i = 0; i < mFlingAnimations.size(); i++) { + mFlingAnimations.get(i).start(); + } + } + + @Override + public void onClick(@NonNull FloatingTaskView v) { + if (v.isStashed()) { + final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); + final boolean viewOnLeftSide = v.getTranslationX() < centerX; + final float destinationRelativeX = viewOnLeftSide + ? mPositionRect.left + : mPositionRect.right - v.getWidth(); + final float minimumVelocityToReachEdge = + getMinimumVelocityToReachEdge(v, destinationRelativeX); + populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax); + + cancelAnyAnimations(v); + + FlingAnimation flingAnimation = new FlingAnimation(v, + DynamicAnimation.TRANSLATION_X); + flingAnimation.setFriction(FLING_FRICTION) + .setStartVelocity(minimumVelocityToReachEdge) + .setMinValue(mMinMax[0]) + .setMaxValue(mMinMax[1]) + .addEndListener((animation, canceled, value, velocity) -> { + if (canceled) return; + mController.setLastPosition((int) v.getTranslationX(), + (int) v.getTranslationY()); + v.setStashed(false); + v.updateLocation(); + }); + mFlingAnimations = new ArrayList<>(); + mFlingAnimations.add(flingAnimation); + flingAnimation.start(); + } + } + + public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, + boolean wasFlungOut) { + if (wasFlungOut) { + snapTaskViewToEdge(v, velX, /* shouldStash= */ false); + } else { + // TODO: use this for something / to spring the view to the touch location + mSpringToTouchOnNextMotionEvent = true; + } + } + + public void stashTaskView(FloatingTaskView v, boolean shouldStash) { + if (v.isStashed() == shouldStash) { + return; + } + final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); + final boolean viewOnLeftSide = v.getTranslationX() < centerX; + snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash); + } + + public boolean isStashedPosition(View v) { + return v.getTranslationX() < mPositionRect.left + || v.getTranslationX() + v.getWidth() > mPositionRect.right; + } + + // TODO: a lot of this is duplicated in onUp -- can it be unified? + private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) { + final boolean movingLeft = velX < ESCAPE_VELOCITY; + populateMinMax(v, movingLeft, shouldStash, mMinMax); + float destinationRelativeX = movingLeft + ? mMinMax[0] + : mMinMax[1]; + + // TODO: why is this necessary / when does this happen? + if (mMinMax[1] < v.getTranslationX()) { + mMinMax[1] = v.getTranslationX(); + } + if (v.getTranslationX() < mMinMax[0]) { + mMinMax[0] = v.getTranslationX(); + } + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity + // so that it'll make it all the way to the side of the screen. + final float minimumVelocityToReachEdge = + getMinimumVelocityToReachEdge(v, destinationRelativeX); + final float startXVelocity = movingLeft + ? Math.min(minimumVelocityToReachEdge, velX) + : Math.max(minimumVelocityToReachEdge, velX); + + cancelAnyAnimations(v); + + mFlingAnimations = getAnimationForUpEvent(v, + shouldStash, startXVelocity, mMinMax[0], mMinMax[1], + destinationRelativeX); + for (int i = 0; i < mFlingAnimations.size(); i++) { + mFlingAnimations.get(i).start(); + } + } + + private void cancelAnyAnimations(FloatingTaskView v) { + if (mFlingAnimations != null) { + for (int i = 0; i < mFlingAnimations.size(); i++) { + if (mFlingAnimations.get(i).isRunning()) { + mFlingAnimations.get(i).cancel(); + } + } + } + if (mViewPropertyAnimation != null) { + mViewPropertyAnimation.cancel(); + mViewPropertyAnimation = null; + } + } + + private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v, + boolean shouldStash, float startVelX, float minValue, float maxValue, + float destinationRelativeX) { + final float ty = v.getTranslationY(); + final ArrayList<FlingAnimation> animations = new ArrayList<>(); + if (ty != capY(v, ty)) { + // The view was being dismissed so the Y is out of bounds, need to animate that. + FlingAnimation yFlingAnimation = new FlingAnimation(v, + DynamicAnimation.TRANSLATION_Y); + yFlingAnimation.setFriction(FLING_FRICTION) + .setStartVelocity(startVelX) + .setMinValue(mPositionRect.top) + .setMaxValue(mPositionRect.bottom - mTaskViewSize.y); + animations.add(yFlingAnimation); + } + FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X); + flingAnimation.setFriction(FLING_FRICTION) + .setStartVelocity(startVelX) + .setMinValue(minValue) + .setMaxValue(maxValue) + .addEndListener((animation, canceled, value, velocity) -> { + if (canceled) return; + Runnable endAction = () -> { + v.setStashed(shouldStash); + v.updateLocation(); + if (!v.isStashed()) { + mController.setLastPosition((int) v.getTranslationX(), + (int) v.getTranslationY()); + } + }; + if (!shouldStash) { + final int xTranslation = (int) v.getTranslationX(); + if (xTranslation != destinationRelativeX) { + // TODO: this animation doesn't feel great, should figure out + // a better way to do this or remove the need for it all together. + mViewPropertyAnimation = v.animate() + .translationX(destinationRelativeX) + .setListener(getAnimationListener(endAction)); + mViewPropertyAnimation.start(); + } else { + endAction.run(); + } + } else { + endAction.run(); + } + }); + animations.add(flingAnimation); + return animations; + } + + private AnimatorListenerAdapter getAnimationListener(Runnable endAction) { + return new AnimatorListenerAdapter() { + boolean translationCanceled = false; + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + translationCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (!translationCanceled) { + endAction.run(); + } + } + }; + } + + private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash, + float[] out) { + if (shouldStash) { + out[0] = onLeft + ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed + : mPositionRect.right - v.getWidth(); + out[1] = onLeft + ? mPositionRect.left + : mPositionRect.right - mOverhangWhenStashed; + } else { + out[0] = mPositionRect.left; + out[1] = mPositionRect.right - mTaskViewSize.x; + } + } + + private float getMinimumVelocityToReachEdge(FloatingTaskView v, + float destinationRelativeX) { + // Minimum velocity required for the view to make it to the targeted side of the screen, + // taking friction into account (4.2f is the number that friction scalars are multiplied + // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the + // animation at the end will ensure that it reaches the destination X regardless. + return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f); + } + + private float capX(FloatingTaskView v, float x, boolean isMoving) { + final int width = v.getWidth(); + if (v.isStashed() || isMoving) { + if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) { + return mPositionRect.left - v.getWidth() + mOverhangWhenStashed; + } + if (x > mPositionRect.right - mOverhangWhenStashed) { + return mPositionRect.right - mOverhangWhenStashed; + } + } else { + if (x < mPositionRect.left) { + return mPositionRect.left; + } + if (x > mPositionRect.right - width) { + return mPositionRect.right - width; + } + } + return x; + } + + private float capY(FloatingTaskView v, float y) { + final int height = v.getHeight(); + if (y < mPositionRect.top) { + return mPositionRect.top; + } + if (y > mPositionRect.bottom - height) { + return mPositionRect.bottom - height; + } + return y; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java new file mode 100644 index 000000000000..581204a82ec7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating.views; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Outline; +import android.graphics.Rect; +import android.os.RemoteException; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskView; +import com.android.wm.shell.TaskViewTransitions; +import com.android.wm.shell.bubbles.RelativeTouchListener; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.floating.FloatingTasksController; + +/** + * A view that holds a floating task using {@link TaskView} along with additional UI to manage + * the task. + */ +public class FloatingTaskView extends FrameLayout { + + private static final String TAG = FloatingTaskView.class.getSimpleName(); + + private FloatingTasksController mController; + + private FloatingMenuView mMenuView; + private int mMenuHeight; + private TaskView mTaskView; + + private float mCornerRadius = 0f; + private int mBackgroundColor; + + private FloatingTasksController.Task mTask; + + private boolean mIsStashed; + + /** + * Creates a floating task view. + * + * @param context the context to use. + * @param controller the controller to notify about changes in the floating task (e.g. removal). + */ + public FloatingTaskView(Context context, FloatingTasksController controller) { + super(context); + mController = controller; + setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation)); + mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size); + mMenuView = new FloatingMenuView(context); + addView(mMenuView); + + applyThemeAttrs(); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); + } + }); + } + + // TODO: call this when theme/config changes + void applyThemeAttrs() { + boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( + mContext.getResources()); + final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + android.R.attr.dialogCornerRadius, + android.R.attr.colorBackgroundFloating}); + mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; + mCornerRadius = mCornerRadius / 2f; + mBackgroundColor = ta.getColor(1, Color.WHITE); + + ta.recycle(); + + mMenuView.setCornerRadius(mCornerRadius); + mMenuHeight = getResources().getDimensionPixelSize( + R.dimen.floating_task_menu_size); + + if (mTaskView != null) { + mTaskView.setCornerRadius(mCornerRadius); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + // Add corner radius here so that the menu extends behind the rounded corners of TaskView. + int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); + measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, + MeasureSpec.getMode(heightMeasureSpec))); + + if (mTaskView != null) { + int taskViewHeight = height - menuViewHeight; + measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, + MeasureSpec.getMode(heightMeasureSpec))); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // Drag handle above + final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); + mMenuView.layout(l, t, r, dragHandleBottom); + if (mTaskView != null) { + // Subtract radius so that the menu extends behind the rounded corners of TaskView. + mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, + dragHandleBottom + mTaskView.getMeasuredHeight()); + } + } + + /** + * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work. + */ + public void createTaskView(Context context, ShellTaskOrganizer organizer, + TaskViewTransitions transitions, SyncTransactionQueue syncQueue) { + mTaskView = new TaskView(context, organizer, transitions, syncQueue); + addView(mTaskView); + mTaskView.setEnableSurfaceClipping(true); + mTaskView.setCornerRadius(mCornerRadius); + } + + /** + * Starts the provided task in the TaskView, if the TaskView exists. This should be called after + * {@link #createTaskView}. + */ + public void startTask(@ShellMainThread ShellExecutor executor, + FloatingTasksController.Task task) { + if (mTaskView == null) { + Log.e(TAG, "starting task before creating the view!"); + return; + } + mTask = task; + mTaskView.setListener(executor, mTaskViewListener); + } + + /** + * Sets the touch handler for the view. + * + * @param handler the touch handler for the view. + */ + public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) { + setOnTouchListener(new RelativeTouchListener() { + @Override + public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { + handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY()); + return true; + } + + @Override + public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy) { + handler.onMove(FloatingTaskView.this, ev, dx, dy); + } + + @Override + public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy, float velX, float velY) { + handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY); + } + }); + setOnClickListener(view -> { + handler.onClick(FloatingTaskView.this); + }); + + mMenuView.addMenuItem(null, view -> { + if (mIsStashed) { + // If we're stashed all clicks un-stash. + handler.onClick(FloatingTaskView.this); + } + }); + } + + private void setContentVisibility(boolean visible) { + if (mTaskView == null) return; + mTaskView.setAlpha(visible ? 1f : 0f); + } + + /** + * Sets the alpha of both this view and the TaskView. + */ + public void setTaskViewAlpha(float alpha) { + if (mTaskView != null) { + mTaskView.setAlpha(alpha); + } + setAlpha(alpha); + } + + /** + * Call when the location or size of the view has changed to update TaskView. + */ + public void updateLocation() { + if (mTaskView == null) return; + mTaskView.onLocationChanged(); + } + + private void updateMenuColor() { + ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); + int color = info != null ? info.taskDescription.getBackgroundColor() : -1; + if (color != -1) { + mMenuView.setBackgroundColor(color); + } else { + mMenuView.setBackgroundColor(mBackgroundColor); + } + } + + /** + * Sets whether the view is stashed or not. + * + * Also updates the touchable area based on this. If the view is stashed we don't direct taps + * on the activity to the activity, instead a tap will un-stash the view. + */ + public void setStashed(boolean isStashed) { + if (mIsStashed != isStashed) { + mIsStashed = isStashed; + if (mTaskView == null) { + return; + } + updateObscuredTouchRect(); + } + } + + /** Whether the view is stashed at the edge of the screen or not. **/ + public boolean isStashed() { + return mIsStashed; + } + + private void updateObscuredTouchRect() { + if (mIsStashed) { + Rect tmpRect = new Rect(); + getBoundsOnScreen(tmpRect); + mTaskView.setObscuredTouchRect(tmpRect); + } else { + mTaskView.setObscuredTouchRect(null); + } + } + + /** + * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has + * been called on this view or if + * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called. + */ + public boolean needsTaskStarted() { + // If the task needs to be restarted then TaskView would have been cleaned up. + return mTaskView == null; + } + + /** Call this when the floating task activity is no longer in use. */ + public void cleanUpTaskView() { + if (mTask != null && mTask.taskId != INVALID_TASK_ID) { + try { + ActivityTaskManager.getService().removeTask(mTask.taskId); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + if (mTaskView != null) { + mTaskView.release(); + removeView(mTaskView); + mTaskView = null; + } + } + + // TODO: use task background colour / how to get the taskInfo ? + private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getStatusBarColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); + } + + private final TaskView.Listener mTaskViewListener = new TaskView.Listener() { + private boolean mInitialized = false; + private boolean mDestroyed = false; + + @Override + public void onInitialized() { + if (mDestroyed || mInitialized) { + return; + } + // Custom options so there is no activity transition animation + ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), + /* enterResId= */ 0, /* exitResId= */ 0); + + Rect launchBounds = new Rect(); + mTaskView.getBoundsOnScreen(launchBounds); + + try { + options.setTaskAlwaysOnTop(true); + if (mTask.intent != null) { + Intent fillInIntent = new Intent(); + // Apply flags to make behaviour match documentLaunchMode=always. + fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + + PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent, + PendingIntent.FLAG_MUTABLE, + null); + mTaskView.startActivity(pi, fillInIntent, options, launchBounds); + } else { + ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent"); + } + } catch (RuntimeException e) { + ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s", + e.getMessage()); + mController.removeTask(); + } + mInitialized = true; + } + + @Override + public void onReleased() { + mDestroyed = true; + } + + @Override + public void onTaskCreated(int taskId, ComponentName name) { + mTask.taskId = taskId; + updateMenuColor(); + setContentVisibility(true); + } + + @Override + public void onTaskVisibilityChanged(int taskId, boolean visible) { + setContentVisibility(visible); + } + + @Override + public void onTaskRemovalStarted(int taskId) { + // Must post because this is called from a binder thread. + post(() -> { + mController.removeTask(); + cleanUpTaskView(); + }); + } + + @Override + public void onBackPressedOnTaskRoot(int taskId) { + if (mTask.taskId == taskId && !mIsStashed) { + // TODO: is removing the window the desired behavior? + post(() -> mController.removeTask()); + } + } + }; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java index 29434f73e84b..fa0061982c45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java @@ -83,7 +83,9 @@ public class PipUtils { public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) { if (action1 == action2) return true; if (action1 == null || action2 == null) return false; - return Objects.equals(action1.getTitle(), action2.getTitle()) + return action1.isEnabled() == action2.isEnabled() + && action1.shouldShowIcon() == action2.shouldShowIcon() + && Objects.equals(action1.getTitle(), action2.getTitle()) && Objects.equals(action1.getContentDescription(), action2.getContentDescription()) && Objects.equals(action1.getActionIntent(), action2.getActionIntent()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index ca22882187d8..1651f8993dc1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -87,6 +87,7 @@ public class TvPipBoundsState extends PipBoundsState { public void resetTvPipState() { mTvFixedPipOrientation = ORIENTATION_UNDETERMINED; mTvPipGravity = DEFAULT_TV_GRAVITY; + mTvPipManuallyCollapsed = false; } /** Set the tv expanded bounds of PiP */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 3fef82352728..c52ed249c2ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -48,6 +48,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index ff4b2edb7310..f8799945134f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,6 +24,7 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskInfo; +import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.util.Slog; @@ -327,6 +328,28 @@ public class RecentTasksController implements TaskStackListenerCallback, return recentTasks; } + /** + * Find the background task that match the given component. + */ + @Nullable + public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) { + if (componentName == null) { + return null; + } + List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RecentTaskInfo task = tasks.get(i); + if (task.isVisible) { + continue; + } + if (componentName.equals(task.baseIntent.getComponent())) { + return task; + } + } + return null; + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 3758471664cc..07a6895e2720 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -133,6 +133,20 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} + public static final int ENTER_REASON_UNKNOWN = 0; + public static final int ENTER_REASON_MULTI_INSTANCE = 1; + public static final int ENTER_REASON_DRAG = 2; + public static final int ENTER_REASON_LAUNCHER = 3; + /** Acts as a mapping to the actual EnterReasons as defined in the logging proto */ + @IntDef(value = { + ENTER_REASON_MULTI_INSTANCE, + ENTER_REASON_DRAG, + ENTER_REASON_LAUNCHER, + ENTER_REASON_UNKNOWN + }) + public @interface SplitEnterReason { + } + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final ShellTaskOrganizer mTaskOrganizer; @@ -371,6 +385,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct); + mSyncQueue.queue(evictWct); } }; options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, @@ -394,7 +411,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, */ public void startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) { - mStageCoordinator.getLogger().enterRequested(instanceId); + mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER); startShortcut(packageName, shortcutId, position, options, user); } @@ -442,7 +459,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, */ public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) { - mStageCoordinator.getLogger().enterRequested(instanceId); + mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER); startIntent(intent, fillInIntent, position, options); } @@ -458,8 +475,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the - // split. + // split and there is no reusable background task. if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { + final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent() + ? mRecentTasksOptional.get().findTaskInBackground( + intent.getIntent().getComponent()) + : null; + if (taskInfo != null) { + startTask(taskInfo.taskId, position, options); + return; + } fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index 626ccb1d2890..2dc4a0441b06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -17,6 +17,8 @@ package com.android.wm.shell.splitscreen; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; @@ -28,6 +30,10 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_DRAG; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; @@ -38,6 +44,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; +import android.annotation.Nullable; import android.util.Slog; import com.android.internal.logging.InstanceId; @@ -59,7 +66,7 @@ public class SplitscreenEventLogger { // Drag info private @SplitPosition int mDragEnterPosition; - private InstanceId mEnterSessionId; + private @Nullable InstanceId mEnterSessionId; // For deduping async events private int mLastMainStagePosition = -1; @@ -67,6 +74,7 @@ public class SplitscreenEventLogger { private int mLastSideStagePosition = -1; private int mLastSideStageUid = -1; private float mLastSplitRatio = -1f; + private @SplitScreenController.SplitEnterReason int mEnterReason = ENTER_REASON_UNKNOWN; public SplitscreenEventLogger() { mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE); @@ -79,20 +87,35 @@ public class SplitscreenEventLogger { return mLoggerSessionId != null; } + public boolean isEnterRequestedByDrag() { + return mEnterReason == ENTER_REASON_DRAG; + } + /** * May be called before logEnter() to indicate that the session was started from a drag. */ public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) { mDragEnterPosition = position; - enterRequested(enterSessionId); + enterRequested(enterSessionId, ENTER_REASON_DRAG); } /** * May be called before logEnter() to indicate that the session was started from launcher. * This specifically is for all the scenarios where split started without a drag interaction */ - public void enterRequested(InstanceId enterSessionId) { + public void enterRequested(@Nullable InstanceId enterSessionId, + @SplitScreenController.SplitEnterReason int enterReason) { mEnterSessionId = enterSessionId; + mEnterReason = enterReason; + } + + /** + * @return if an enterSessionId has been set via either + * {@link #enterRequested(InstanceId, int)} or + * {@link #enterRequestedByDrag(int, InstanceId)} + */ + public boolean hasValidEnterSessionId() { + return mEnterSessionId != null; } /** @@ -103,9 +126,7 @@ public class SplitscreenEventLogger { @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) { mLoggerSessionId = mIdSequence.newInstanceId(); - int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED - ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape) - : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER; + int enterReason = getLoggerEnterReason(isLandscape); updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), mainStageUid); updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), @@ -124,6 +145,20 @@ public class SplitscreenEventLogger { mLoggerSessionId.getId()); } + private int getLoggerEnterReason(boolean isLandscape) { + switch (mEnterReason) { + case ENTER_REASON_MULTI_INSTANCE: + return SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE; + case ENTER_REASON_LAUNCHER: + return SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER; + case ENTER_REASON_DRAG: + return getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape); + case ENTER_REASON_UNKNOWN: + default: + return SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER; + } + } + /** * Returns the framework logging constant given a splitscreen exit reason. */ @@ -189,6 +224,7 @@ public class SplitscreenEventLogger { mLastMainStageUid = -1; mLastSideStagePosition = -1; mLastSideStageUid = -1; + mEnterReason = ENTER_REASON_UNKNOWN; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 25793ed0317b..c17f8226c925 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -18,6 +18,8 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -44,6 +46,8 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER; +import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; @@ -501,6 +505,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); + // If split still not active, apply windows bounds first to avoid surface reset to + // wrong pos by SurfaceAnimator from wms. + if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) { + updateWindowBounds(mSplitLayout, wct); + } + wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -669,7 +679,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void setEnterInstanceId(InstanceId instanceId) { if (instanceId != null) { - mLogger.enterRequested(instanceId); + mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER); } } @@ -1107,6 +1117,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void addActivityOptions(Bundle opts, StageTaskListener stage) { opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); + // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split + // will be canceled. + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); } void updateActivityOptions(Bundle opts, @SplitPosition int position) { @@ -1453,18 +1467,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } } else if (isSideStage && hasChildren && !mMainStage.isActive()) { - // TODO (b/238697912) : Add the validation to prevent entering non-recovered status - onSplitScreenEnter(); final WindowContainerTransaction wct = new WindowContainerTransaction(); mSplitLayout.init(); - mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); - mMainStage.activate(wct, true /* includingTopTask */); - updateWindowBounds(mSplitLayout, wct); - wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + if (mLogger.isEnterRequestedByDrag()) { + prepareEnterSplitScreen(wct); + } else { + // TODO (b/238697912) : Add the validation to prevent entering non-recovered status + onSplitScreenEnter(); + mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + mMainStage.activate(wct, true /* includingTopTask */); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); + } + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { - mSplitLayout.flingDividerToCenter(); + if (mLogger.isEnterRequestedByDrag()) { + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + } else { + mSplitLayout.flingDividerToCenter(); + } }); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { @@ -1472,6 +1495,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateRecentTasksSplitPair(); if (!mLogger.hasStartedSession()) { + if (!mLogger.hasValidEnterSessionId()) { + mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE); + } mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 3ca5b9c38aff..d6adaa7d533b 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -48,6 +48,6 @@ android_test { "wm-flicker-common-assertions", "wm-flicker-common-app-helpers", "platform-test-annotations", - "wmshell-flicker-test-components", + "flickertestapplib", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 574a9f4da627..1284c41af855 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -19,7 +19,7 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="WMShellFlickerTests.apk"/> - <option name="test-file-name" value="WMShellFlickerTestApp.apk" /> + <option name="test-file-name" value="FlickerTestApp.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.wm.shell.flicker"/> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 55979905cc4f..2ccf1216d7c1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -114,14 +114,14 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag( ) { assertLayers { if (isShellTransitionsEnabled) { - this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) .then() // TODO(b/245472831): Verify the component should snap to divider. .isVisible(component) } else { - this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) .then() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt new file mode 100644 index 000000000000..c045325f19c3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt @@ -0,0 +1,55 @@ +/* + * 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.wm.shell.flicker + +import android.app.Instrumentation +import android.content.Context +import android.provider.Settings +import android.util.Log +import com.android.compatibility.common.util.SystemUtil +import java.io.IOException + +object MultiWindowUtils { + private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { + try { + SystemUtil.runShellCommand(instrumentation, cmd) + } catch (e: IOException) { + Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e") + } + } + + fun getDevEnableNonResizableMultiWindow(context: Context): Int = + Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + + fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) = + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue) + + fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) = + executeShellCommand( + instrumentation, + createConfigSupportsNonResizableMultiWindowCommand(configValue)) + + fun resetMultiWindowConfig(instrumentation: Instrumentation) = + executeShellCommand(instrumentation, resetMultiWindowConfigCommand) + + private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String = + "wm set-multi-window-config --supportsNonResizable $configValue" + + private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config" +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 1390334f7938..cbe085be8952 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.flicker.bubble import android.app.INotificationManager import android.app.NotificationManager import android.content.Context +import android.content.pm.PackageManager import android.os.ServiceManager import android.view.Surface import androidx.test.uiautomator.By @@ -28,9 +29,9 @@ import com.android.server.wm.flicker.Flicker import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.LaunchBubbleHelper import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE import com.android.wm.shell.flicker.BaseTest -import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper import org.junit.runners.Parameterized /** @@ -47,7 +48,7 @@ abstract class BaseBubbleScreen( ServiceManager.getService(Context.NOTIFICATION_SERVICE)) private val uid = context.packageManager.getApplicationInfo( - testApp.`package`, 0).uid + testApp.`package`, PackageManager.ApplicationInfoFlags.of(0)).uid @JvmOverloads protected open fun buildTransition( diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index effd33095530..9a6fd1103766 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -16,8 +16,9 @@ package com.android.wm.shell.flicker.bubble -import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 @@ -47,10 +48,15 @@ open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen transitions { val addBubbleBtn = waitAndGetAddBubbleBtn() addBubbleBtn?.click() ?: error("Bubble widget not found") + + device.wait( + Until.findObjects( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME) + ), FIND_OBJECT_TIMEOUT + ) ?: error("No bubbles found") } } - @Presubmit @Test open fun testAppIsAlwaysVisible() { testSpec.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt deleted file mode 100644 index 01ba9907c24c..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import android.content.pm.PackageManager.FEATURE_LEANBACK -import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY -import android.support.test.launcherhelper.LauncherStrategyFactory -import android.util.Log -import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until -import com.android.compatibility.common.util.SystemUtil -import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.traces.common.IComponentNameMatcher -import java.io.IOException - -abstract class BaseAppHelper( - instrumentation: Instrumentation, - launcherName: String, - component: IComponentNameMatcher -) : StandardAppHelper( - instrumentation, - launcherName, - component, - LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy -) { - private val appSelector = By.pkg(`package`).depth(0) - - protected val isTelevision: Boolean - get() = context.packageManager.run { - hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY) - } - - val ui: UiObject2? - get() = uiDevice.findObject(appSelector) - - fun waitUntilClosed(): Boolean { - return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS) - } - - companion object { - private const val APP_CLOSE_WAIT_TIME_MS = 3_000L - - fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { - try { - SystemUtil.runShellCommand(instrumentation, cmd) - } catch (e: IOException) { - Log.e("BaseAppHelper", "executeShellCommand error! $e") - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt deleted file mode 100644 index 471e010cf560..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components - -class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.FixedActivity.LABEL, - Components.FixedActivity.COMPONENT.toFlickerComponent() -)
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt deleted file mode 100644 index 2e690de666f4..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.helpers.FIND_TIMEOUT -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.testapp.Components - -open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.ImeActivity.LABEL, - Components.ImeActivity.COMPONENT.toFlickerComponent() -) { - /** - * Opens the IME and wait for it to be displayed - * - * @param wmHelper Helper used to wait for WindowManager states - */ - open fun openIME(wmHelper: WindowManagerStateHelper) { - if (!isTelevision) { - val editText = uiDevice.wait( - Until.findObject(By.res(getPackage(), "plain_text_input")), - FIND_TIMEOUT) - - require(editText != null) { - "Text field not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" - } - editText.click() - wmHelper.StateSyncBuilder() - .withImeShown() - .waitForAndVerify() - } else { - // If we do the same thing as above - editText.click() - on TV, that's going to force TV - // into the touch mode. We really don't want that. - launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME) - } - } - - /** - * Opens the IME and wait for it to be gone - * - * @param wmHelper Helper used to wait for WindowManager states - */ - open fun closeIME(wmHelper: WindowManagerStateHelper) { - if (!isTelevision) { - uiDevice.pressBack() - // Using only the AccessibilityInfo it is not possible to identify if the IME is active - wmHelper.StateSyncBuilder() - .withImeGone() - .waitForAndVerify() - } else { - // While pressing the back button should close the IME on TV as well, it may also lead - // to the app closing. So let's instead just ask the app to close the IME. - launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt deleted file mode 100644 index 1b8a44bbe78e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components - -class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper( - instrumentation, - Components.LaunchBubbleActivity.LABEL, - Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent() -) { - - companion object { - const val TIMEOUT_MS = 3_000L - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt deleted file mode 100644 index 52e5d7e14ab9..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.helpers - -import android.app.Instrumentation -import android.graphics.Point -import android.os.SystemClock -import android.view.InputDevice -import android.view.MotionEvent -import android.view.ViewConfiguration -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.traces.common.IComponentMatcher -import com.android.server.wm.traces.common.IComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.testapp.Components - -class SplitScreenHelper( - instrumentation: Instrumentation, - activityLabel: String, - componentInfo: IComponentNameMatcher -) : BaseAppHelper(instrumentation, activityLabel, componentInfo) { - - companion object { - const val TIMEOUT_MS = 3_000L - const val DRAG_DURATION_MS = 1_000L - const val NOTIFICATION_SCROLLER = "notification_stack_scroller" - const val DIVIDER_BAR = "docked_divider_handle" - const val GESTURE_STEP_MS = 16L - const val LONG_PRESS_TIME_MS = 100L - - private val notificationScrollerSelector: BySelector - get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) - private val notificationContentSelector: BySelector - get() = By.text("Notification content") - private val dividerBarSelector: BySelector - get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) - - fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT.toFlickerComponent() - ) - - fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SplitScreenSecondaryActivity.LABEL, - Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent() - ) - - fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.NonResizeableActivity.LABEL, - Components.NonResizeableActivity.COMPONENT.toFlickerComponent() - ) - - fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.SendNotificationActivity.LABEL, - Components.SendNotificationActivity.COMPONENT.toFlickerComponent() - ) - - fun getIme(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper( - instrumentation, - Components.ImeActivity.LABEL, - Components.ImeActivity.COMPONENT.toFlickerComponent() - ) - - fun waitForSplitComplete( - wmHelper: WindowManagerStateHelper, - primaryApp: IComponentMatcher, - secondaryApp: IComponentMatcher, - ) { - wmHelper.StateSyncBuilder() - .withWindowSurfaceAppeared(primaryApp) - .withWindowSurfaceAppeared(secondaryApp) - .withSplitDividerVisible() - .waitForAndVerify() - } - - fun enterSplit( - wmHelper: WindowManagerStateHelper, - tapl: LauncherInstrumentation, - primaryApp: SplitScreenHelper, - secondaryApp: SplitScreenHelper - ) { - tapl.workspace.switchToOverview().dismissAllTasks() - primaryApp.launchViaIntent(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - tapl.goHome() - wmHelper.StateSyncBuilder() - .withHomeActivityVisible() - .waitForAndVerify() - splitFromOverview(tapl) - waitForSplitComplete(wmHelper, primaryApp, secondaryApp) - } - - fun splitFromOverview(tapl: LauncherInstrumentation) { - // Note: The initial split position in landscape is different between tablet and phone. - // In landscape, tablet will let the first app split to right side, and phone will - // split to left side. - if (tapl.isTablet) { - tapl.workspace.switchToOverview().overviewActions - .clickSplit() - .currentTask - .open() - } else { - tapl.workspace.switchToOverview().currentTask - .tapMenu() - .tapSplitMenuItem() - .currentTask - .open() - } - SystemClock.sleep(TIMEOUT_MS) - } - - fun dragFromNotificationToSplit( - instrumentation: Instrumentation, - device: UiDevice, - wmHelper: WindowManagerStateHelper - ) { - val displayBounds = wmHelper.currentState.layerState - .displays.firstOrNull { !it.isVirtual } - ?.layerStackSpace - ?: error("Display not found") - - // Pull down the notifications - device.swipe( - displayBounds.centerX(), 5, - displayBounds.centerX(), displayBounds.bottom, 20 /* steps */ - ) - SystemClock.sleep(TIMEOUT_MS) - - // Find the target notification - val notificationScroller = device.wait( - Until.findObject(notificationScrollerSelector), TIMEOUT_MS - ) - var notificationContent = notificationScroller.findObject(notificationContentSelector) - - while (notificationContent == null) { - device.swipe( - displayBounds.centerX(), displayBounds.centerY(), - displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */ - ) - notificationContent = notificationScroller.findObject(notificationContentSelector) - } - - // Drag to split - val dragStart = notificationContent.visibleCenter - val dragMiddle = Point(dragStart.x + 50, dragStart.y) - val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) - val downTime = SystemClock.uptimeMillis() - - touch( - instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, - TIMEOUT_MS, dragStart - ) - // It needs a horizontal movement to trigger the drag - touchMove( - instrumentation, downTime, SystemClock.uptimeMillis(), - DRAG_DURATION_MS, dragStart, dragMiddle - ) - touchMove( - instrumentation, downTime, SystemClock.uptimeMillis(), - DRAG_DURATION_MS, dragMiddle, dragEnd - ) - // Wait for a while to start splitting - SystemClock.sleep(TIMEOUT_MS) - touch( - instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(), - GESTURE_STEP_MS, dragEnd - ) - SystemClock.sleep(TIMEOUT_MS) - } - - fun touch( - instrumentation: Instrumentation, - action: Int, - downTime: Long, - eventTime: Long, - duration: Long, - point: Point - ) { - val motionEvent = MotionEvent.obtain( - downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0 - ) - motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN - instrumentation.uiAutomation.injectInputEvent(motionEvent, true) - motionEvent.recycle() - SystemClock.sleep(duration) - } - - fun touchMove( - instrumentation: Instrumentation, - downTime: Long, - eventTime: Long, - duration: Long, - from: Point, - to: Point - ) { - val steps: Long = duration / GESTURE_STEP_MS - var currentTime = eventTime - var currentX = from.x.toFloat() - var currentY = from.y.toFloat() - val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() - val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() - - for (i in 1..steps) { - val motionMove = MotionEvent.obtain( - downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0 - ) - motionMove.source = InputDevice.SOURCE_TOUCHSCREEN - instrumentation.uiAutomation.injectInputEvent(motionMove, true) - motionMove.recycle() - - currentTime += GESTURE_STEP_MS - if (i == steps - 1) { - currentX = to.x.toFloat() - currentY = to.y.toFloat() - } else { - currentX += stepX - currentY += stepY - } - SystemClock.sleep(GESTURE_STEP_MS) - } - } - - fun longPress( - instrumentation: Instrumentation, - point: Point - ) { - val downTime = SystemClock.uptimeMillis() - touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point) - SystemClock.sleep(LONG_PRESS_TIME_MS) - touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point) - } - - fun createShortcutOnHotseatIfNotExist( - tapl: LauncherInstrumentation, - appName: String - ) { - tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) - val allApps = tapl.workspace.switchToAllApps() - allApps.freeze() - try { - allApps.getAppIcon(appName).dragToHotseat(0) - } finally { - allApps.unfreeze() - } - } - - fun dragDividerToResizeAndWait( - device: UiDevice, - wmHelper: WindowManagerStateHelper - ) { - val displayBounds = wmHelper.currentState.layerState - .displays.firstOrNull { !it.isVirtual } - ?.layerStackSpace - ?: error("Display not found") - val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3)) - - wmHelper.StateSyncBuilder() - .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) - .waitForAndVerify() - } - - fun dragDividerToDismissSplit( - device: UiDevice, - wmHelper: WindowManagerStateHelper, - dragToRight: Boolean, - dragToBottom: Boolean - ) { - val displayBounds = wmHelper.currentState.layerState - .displays.firstOrNull { !it.isVirtual } - ?.layerStackSpace - ?: error("Display not found") - val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point( - if (dragToRight) { - displayBounds.width * 4 / 5 - } else { - displayBounds.width * 1 / 5 - }, - if (dragToBottom) { - displayBounds.height * 4 / 5 - } else { - displayBounds.height * 1 / 5 - })) - } - - fun doubleTapDividerToSwitch(device: UiDevice) { - val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - val interval = (ViewConfiguration.getDoubleTapTimeout() + - ViewConfiguration.getDoubleTapMinTime()) / 2 - dividerBar.click() - SystemClock.sleep(interval.toLong()) - dividerBar.click() - } - - fun copyContentInSplit( - instrumentation: Instrumentation, - device: UiDevice, - sourceApp: IComponentNameMatcher, - destinationApp: IComponentNameMatcher, - ) { - // Copy text from sourceApp - val textView = device.wait(Until.findObject( - By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS) - longPress(instrumentation, textView.getVisibleCenter()) - - val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) - copyBtn.click() - - // Paste text to destinationApp - val editText = device.wait(Until.findObject( - By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS) - longPress(instrumentation, editText.getVisibleCenter()) - - val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) - pasteBtn.click() - - // Verify text - if (!textView.getText().contentEquals(editText.getText())) { - error("Fail to copy content in split") - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 87d800c3dc6a..4788507c1443 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -28,16 +28,17 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP +import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT -import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION -import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -72,7 +73,7 @@ import org.junit.runners.Parameterized class EnterPipToOtherOrientationTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { - private val testApp = FixedAppHelper(instrumentation) + private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) @@ -127,7 +128,7 @@ class EnterPipToOtherOrientationTest( } /** - * Checks that the [ComponentMatcher.NAV_BAR] has the correct position at + * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at * the start and end of the transition */ @FlakyTest diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index 45851c8b6326..628599160c65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -18,14 +18,14 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter -import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import org.junit.Test /** * Base class for pip expand tests */ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - protected val testApp = FixedAppHelper(instrumentation) + protected val testApp = FixedOrientationAppHelper(instrumentation) /** * Checks that the pip app window remains inside the display bounds throughout the whole diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 19bdca51f8af..5f9419694c13 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.server.wm.flicker.traces.region.RegionSubject import com.android.wm.shell.flicker.Direction -import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Test /** @@ -29,7 +29,7 @@ import org.junit.Test abstract class MovePipShelfHeightTransition( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { - protected val testApp = FixedAppHelper(instrumentation) + protected val testApp = FixedOrientationAppHelper(instrumentation) /** * Checks [pipApp] window remains visible throughout the animation diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 3d3b53de3dc8..2aa0da95dfd5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -24,11 +24,11 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.ComponentNameMatcher -import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index f13698f1d04e..0fce64ea073b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -25,10 +25,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -62,7 +62,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - private val fixedApp = FixedAppHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation) @@ -74,7 +74,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { - fixedApp.launchViaIntent(wmHelper) + testApp.launchViaIntent(wmHelper) setRotation(testSpec.startRotation) } transitions { @@ -90,48 +90,48 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() /** - * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition + * Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */ @Presubmit @Test fun fixedAppLayer_StartingBounds() { testSpec.assertLayersStart { - visibleRegion(fixedApp).coversAtMost(screenBoundsStart) + visibleRegion(testApp).coversAtMost(screenBoundsStart) } } /** - * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition + * Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */ @Presubmit @Test fun fixedAppLayer_EndingBounds() { testSpec.assertLayersEnd { - visibleRegion(fixedApp).coversAtMost(screenBoundsEnd) + visibleRegion(testApp).coversAtMost(screenBoundsEnd) } } /** - * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the start + * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start * of the transition */ @Presubmit @Test fun appLayers_StartingBounds() { testSpec.assertLayersStart { - visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsStart) + visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart) } } /** - * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the end + * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end * of the transition */ @Presubmit @Test fun appLayers_EndingBounds() { testSpec.assertLayersEnd { - visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsEnd) + visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) } } @@ -165,26 +165,26 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS } /** - * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the + * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the * transition */ @Presubmit @Test fun pipIsAboveFixedAppWindow_Start() { testSpec.assertWmStart { - isAboveWindow(pipApp, fixedApp) + isAboveWindow(pipApp, testApp) } } /** - * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the + * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the * transition */ @Presubmit @Test fun pipIsAboveFixedAppWindow_End() { testSpec.assertWmEnd { - isAboveWindow(pipApp, fixedApp) + isAboveWindow(pipApp, testApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index b67cf772358b..ff505a04290b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -21,12 +21,12 @@ import android.content.Intent import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.flicker.BaseTest -import com.android.wm.shell.flicker.helpers.PipAppHelper -import com.android.wm.shell.flicker.testapp.Components abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) { protected val pipApp = PipAppHelper(instrumentation) @@ -61,11 +61,11 @@ abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec * * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test) * @param stringExtras Arguments to pass to the PIP launch intent - * @param extraSpec Addicional segment of flicker specification + * @param extraSpec Additional segment of flicker specification */ @JvmOverloads protected open fun buildTransition( - stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), + stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"), extraSpec: FlickerBuilder.() -> Unit = {} ): FlickerBuilder.() -> Unit { return { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 866e4e83ec1e..30332f6c4aaf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -31,9 +31,9 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE -import com.android.wm.shell.flicker.testapp.Components -import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -68,7 +68,7 @@ open class SetRequestedOrientationWhilePinnedTest( pipApp.launchViaIntent(wmHelper, stringExtras = mapOf( EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())) // Enter PiP. - broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP) + broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP) // System bar may fade out during fixed rotation. wmHelper.StateSyncBuilder() .withPipShown() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt new file mode 100644 index 000000000000..cdd768abd5bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip.tv + +import android.app.Instrumentation +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.helpers.PipAppHelper +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +/** + * Helper class for PIP app on AndroidTV + */ +open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) { + private val appSelector = By.pkg(`package`).depth(0) + + val ui: UiObject2? + get() = uiDevice.findObject(appSelector) + + private fun focusOnObject(selector: BySelector): Boolean { + // We expect all the focusable UI elements to be arranged in a way so that it is possible + // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top" + // from "the bottom". + repeat(FOCUS_ATTEMPTS) { + uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true } + ?: error("The object we try to focus on is gone.") + + uiDevice.pressDPadDown() + uiDevice.waitForIdle() + } + return false + } + + override fun clickObject(resId: String) { + val selector = By.res(`package`, resId) + focusOnObject(selector) || error("Could not focus on `$resId` object") + uiDevice.pressDPadCenter() + } + + @Deprecated( + "Use PipAppHelper.closePipWindow(wmHelper) instead", + ReplaceWith("closePipWindow(wmHelper)") + ) + override fun closePipWindow() { + uiDevice.closeTvPipWindow() + } + + /** + * Taps the pip window and dismisses it by clicking on the X button. + */ + override fun closePipWindow(wmHelper: WindowManagerStateHelper) { + uiDevice.closeTvPipWindow() + + // Wait for animation to complete. + wmHelper.StateSyncBuilder() + .withPipGone() + .withHomeActivityVisible() + .waitForAndVerify() + } + + fun waitUntilClosed(): Boolean { + val appSelector = By.pkg(`package`).depth(0) + return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS) + } + + companion object { + private const val FOCUS_ATTEMPTS = 20 + private const val APP_CLOSE_WAIT_TIME_MS = 3_000L + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt index 180ced0a6814..a16f5f6f1620 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt @@ -21,7 +21,6 @@ import android.content.pm.PackageManager import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice -import com.android.wm.shell.flicker.helpers.PipAppHelper import org.junit.Before import org.junit.runners.Parameterized @@ -38,7 +37,7 @@ abstract class PipTestBase( hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY) } } - protected val testApp = PipAppHelper(instrumentation) + protected val testApp = PipAppHelperTv(instrumentation) @Before open fun televisionSetUp() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt index 4be19d61278b..68dbbfb89b6c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt @@ -19,8 +19,8 @@ package com.android.wm.shell.flicker.pip.tv import android.graphics.Rect import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.UiObject2 +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.testapp.Components import com.android.wm.shell.flicker.wait import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -165,44 +165,44 @@ class TvPipMenuTests : TvPipTestBase() { enterPip_openMenu_assertShown() // PiP menu should contain "No-Op", "Off" and "Clear" buttons... - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) ?: fail("\"No-Op\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) ?: fail("\"Off\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) ?: fail("\"Clear\" button should be shown in Pip menu") // ... and should also contain the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) // Invoking the "Off" action should replace it with the "On" action/button and should // remove the "No-Op" action/button. "Clear" action/button should remain in the menu ... - uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON) + uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON) ?: fail("\"On\" button should be shown in Pip for a corresponding custom action") assertNull("\"No-Op\" button should not be shown in Pip menu", uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_NO_OP)) - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + ActivityOptions.Pip.MENU_ACTION_NO_OP)) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) ?: fail("\"Clear\" button should be shown in Pip menu") // ... as well as the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) // Invoking the "Clear" action should remove all the custom actions and their corresponding // buttons, ... uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone( - Components.PipActivity.MENU_ACTION_ON)?.also { + ActivityOptions.Pip.MENU_ACTION_ON)?.also { isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu") } assertNull("\"Off\" button should not be shown in Pip menu", uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_OFF)) + ActivityOptions.Pip.MENU_ACTION_OFF)) assertNull("\"Clear\" button should not be shown in Pip menu", uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_CLEAR)) + ActivityOptions.Pip.MENU_ACTION_CLEAR)) assertNull("\"No-Op\" button should not be shown in Pip menu", uiDevice.findTvPipMenuElementWithDescription( - Components.PipActivity.MENU_ACTION_NO_OP)) + ActivityOptions.Pip.MENU_ACTION_NO_OP)) // ... but the menu should still contain the "Full screen" and "Close" buttons. assertFullscreenAndCloseButtonsAreShown() @@ -217,11 +217,11 @@ class TvPipMenuTests : TvPipTestBase() { enterPip_openMenu_assertShown() // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions... - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP) ?: fail("\"No-Op\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF) ?: fail("\"Off\" button should be shown in Pip menu") - uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) ?: fail("\"Clear\" button should be shown in Pip menu") // ... should also contain the "Full screen" and "Close" buttons, ... assertFullscreenAndCloseButtonsAreShown() @@ -231,7 +231,7 @@ class TvPipMenuTests : TvPipTestBase() { assertNull("\"Pause\" button should not be shown in menu when there are custom actions", uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)) - uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR) + uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR) // Invoking the "Clear" action should remove all the custom actions, which should bring up // media buttons... uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index 3c439fdb9d98..7dbd27949bdd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -28,7 +28,6 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowKeepVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible import org.junit.FixMethodOrder @@ -48,16 +47,16 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - private val textEditApp = SplitScreenHelper.getIme(instrumentation) + private val textEditApp = SplitScreenUtils.getIme(instrumentation) override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp) } transitions { - SplitScreenHelper.copyContentInSplit( + SplitScreenUtils.copyContentInSplit( instrumentation, device, primaryApp, textEditApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index 60e5f78fa724..3646fd7f0177 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible @@ -57,14 +56,14 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } transitions { if (tapl.isTablet) { - SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper, + SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper, dragToRight = false, dragToBottom = true) } else { - SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper, + SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper, dragToRight = true, dragToBottom = true) } wmHelper.StateSyncBuilder() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index 2db3009b8d68..80abedd476e6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -28,7 +28,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appWindowBecomesInvisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible @@ -56,7 +55,7 @@ class DismissSplitScreenByGoHome( get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } transitions { tapl.goHome() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index fddd84cfe4db..2915787d5a43 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -29,7 +29,6 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowKeepVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsChanges import org.junit.FixMethodOrder @@ -54,10 +53,10 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } transitions { - SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper) + SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index a7c689884e98..8e041a703802 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag @@ -76,7 +75,7 @@ class EnterSplitScreenByDragFromAllApps( .openAllApps() .getAppIcon(secondaryApp.appName) .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 7d8a8db1a1ee..2ee12f1f0af2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -21,8 +21,6 @@ import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.WindowManagerPolicyConstants import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -31,7 +29,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag @@ -60,7 +57,7 @@ class EnterSplitScreenByDragFromNotification( testSpec: FlickerTestParameter ) : SplitScreenBase(testSpec) { - private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) @Before fun before() { @@ -74,18 +71,13 @@ class EnterSplitScreenByDragFromNotification( setup { // Send a notification sendNotificationApp.launchViaIntent(wmHelper) - val sendNotification = device.wait( - Until.findObject(By.text("Send Notification")), - SplitScreenHelper.TIMEOUT_MS - ) - sendNotification?.click() ?: error("Send notification button not found") - + sendNotificationApp.postNotification(wmHelper) tapl.goHome() primaryApp.launchViaIntent(wmHelper) } transitions { - SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) } teardown { sendNotificationApp.exit(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index bfd8a3a53437..a11874e00228 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -30,7 +30,6 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag @@ -70,7 +69,7 @@ class EnterSplitScreenByDragFromTaskbar( super.transition(this) setup { tapl.goHome() - SplitScreenHelper.createShortcutOnHotseatIfNotExist( + SplitScreenUtils.createShortcutOnHotseatIfNotExist( tapl, secondaryApp.appName ) primaryApp.launchViaIntent(wmHelper) @@ -79,7 +78,7 @@ class EnterSplitScreenByDragFromTaskbar( tapl.launchedAppState.taskbar .getAppIcon(secondaryApp.appName) .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt index cefb9f52d4fe..6064b52938c8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -26,7 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible @@ -64,8 +63,8 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen .waitForAndVerify() } transitions { - SplitScreenHelper.splitFromOverview(tapl) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.splitFromOverview(tapl) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index eab473ca55a1..e6d6379e750c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -21,12 +21,11 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseTest -import com.android.wm.shell.flicker.helpers.SplitScreenHelper abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) { protected val context: Context = instrumentation.context - protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) - protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) + protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt new file mode 100644 index 000000000000..e57ac91617b7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.IComponentMatcher +import com.android.server.wm.traces.common.IComponentNameMatcher +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME + +internal object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val GESTURE_STEP_MS = 16L + private const val LONG_PRESS_TIME_MS = 100L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = + ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper.StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + tapl.workspace.switchToOverview().dismissAllTasks() + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder() + .withHomeActivityVisible() + .waitForAndVerify() + splitFromOverview(tapl) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + tapl.workspace.switchToOverview().overviewActions + .clickSplit() + .currentTask + .open() + } else { + tapl.workspace.switchToOverview().currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = wmHelper.currentState.layerState + .displays.firstOrNull { !it.isVirtual } + ?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), 5, + displayBounds.centerX(), displayBounds.bottom, 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = device.wait( + Until.findObject(notificationScrollerSelector), TIMEOUT_MS + ) ?: error ("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), displayBounds.centerY(), + displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch( + instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, + TIMEOUT_MS, dragStart + ) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, downTime, SystemClock.uptimeMillis(), + DRAG_DURATION_MS, dragStart, dragMiddle + ) + touchMove( + instrumentation, downTime, SystemClock.uptimeMillis(), + DRAG_DURATION_MS, dragMiddle, dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(), + GESTURE_STEP_MS, dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = MotionEvent.obtain( + downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0 + ) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = MotionEvent.obtain( + downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun longPress( + instrumentation: Instrumentation, + point: Point + ) { + val downTime = SystemClock.uptimeMillis() + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point) + SystemClock.sleep(LONG_PRESS_TIME_MS) + touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point) + } + + fun createShortcutOnHotseatIfNotExist( + tapl: LauncherInstrumentation, + appName: String + ) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait( + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = wmHelper.currentState.layerState + .displays.firstOrNull { !it.isVirtual } + ?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3)) + + wmHelper.StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = wmHelper.currentState.layerState + .displays.firstOrNull { !it.isVirtual } + ?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + })) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = (ViewConfiguration.getDoubleTapTimeout() + + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = device.wait(Until.findObject( + By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS) + longPress(instrumentation, textView.visibleCenter) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + copyBtn.click() + + // Paste text to destinationApp + val editText = device.wait(Until.findObject( + By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS) + longPress(instrumentation, editText.visibleCenter) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 2d5d2b71b270..f06dd66f61d2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -27,9 +27,10 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd @@ -49,22 +50,64 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 -class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { +class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } transitions { - SplitScreenHelper.doubleTapDividerToSwitch(device) + SplitScreenUtils.doubleTapDividerToSwitch(device) wmHelper.StateSyncBuilder() .withAppTransitionIdle() .waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) } } + private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper.StateSyncBuilder().add("appWindowsSwitched") { + val primaryAppWindow = it.wmState.visibleWindows.firstOrNull { window -> + primaryApp.windowMatchesAnyOf(window) + } ?: return@add false + val secondaryAppWindow = it.wmState.visibleWindows.firstOrNull { window -> + secondaryApp.windowMatchesAnyOf(window) + } ?: return@add false + + if (testSpec.startRotation.isRotated()) { + return@add primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } else { + return@add primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } + }.waitForAndVerify() + } + + private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper.StateSyncBuilder().add("appLayersSwitched") { + val primaryAppLayer = it.layerState.visibleLayers.firstOrNull { window -> + primaryApp.layerMatchesAnyOf(window) + } ?: return@add false + val secondaryAppLayer = it.layerState.visibleLayers.firstOrNull { window -> + secondaryApp.layerMatchesAnyOf(window) + } ?: return@add false + + val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds + ?: return@add false + val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds + ?: return@add false + + if (testSpec.startRotation.isRotated()) { + return@add primaryVisibleRegion.right <= secondaryVisibleRegion.left + } else { + return@add primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + }.waitForAndVerify() + } + @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index 20c6af7c0530..5c3011643270 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible @@ -48,13 +47,13 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation) + val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) thirdApp.launchViaIntent(wmHelper) wmHelper.StateSyncBuilder() @@ -63,7 +62,7 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr } transitions { tapl.launchedAppState.quickSwitchToPreviousApp() - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index cb9ca9f13849..9c66a3734da3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible @@ -53,7 +52,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder() @@ -62,7 +61,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas } transitions { tapl.workspace.quickSwitchToPreviousApp() - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index 266276749c13..e8862bd4c982 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible @@ -53,7 +52,7 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB get() = { super.transition(this) setup { - SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder() @@ -64,7 +63,7 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB tapl.workspace.switchToOverview() .currentTask .open() - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp deleted file mode 100644 index ea606df1536d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "WMShellFlickerTestApp", - srcs: ["**/*.java"], - sdk_version: "current", - test_suites: ["device-tests"], -} - -java_library { - name: "wmshell-flicker-test-components", - srcs: ["src/**/Components.java"], - sdk_version: "test_current", -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml deleted file mode 100644 index bc0b0b6292b4..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ /dev/null @@ -1,147 +0,0 @@ -<?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.wm.shell.flicker.testapp"> - - <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> - <application android:allowBackup="false" - android:supportsRtl="true"> - <activity android:name=".FixedActivity" - android:resizeableActivity="true" - android:supportsPictureInPicture="true" - android:launchMode="singleTop" - android:theme="@style/CutoutShortEdges" - android:label="FixedApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity android:name=".PipActivity" - android:resizeableActivity="true" - android:supportsPictureInPicture="true" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" - android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity" - android:theme="@style/CutoutShortEdges" - android:launchMode="singleTop" - android:label="PipApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".ImeActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity" - android:theme="@style/CutoutShortEdges" - android:label="ImeApp" - android:launchMode="singleTop" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SplitScreenActivity" - android:resizeableActivity="true" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity" - android:theme="@style/CutoutShortEdges" - android:label="SplitScreenPrimaryApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SplitScreenSecondaryActivity" - android:resizeableActivity="true" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity" - android:theme="@style/CutoutShortEdges" - android:label="SplitScreenSecondaryApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SendNotificationActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity" - android:theme="@style/CutoutShortEdges" - android:label="SendNotificationApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".NonResizeableActivity" - android:resizeableActivity="false" - android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity" - android:theme="@style/CutoutShortEdges" - android:label="NonResizeableApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <activity android:name=".SimpleActivity" - android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity" - android:theme="@style/CutoutShortEdges" - android:label="SimpleApp" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity - android:name=".LaunchBubbleActivity" - android:label="LaunchBubbleApp" - android:exported="true" - android:theme="@style/CutoutShortEdges" - android:launchMode="singleTop"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <activity - android:name=".BubbleActivity" - android:label="BubbleApp" - android:exported="false" - android:theme="@style/CutoutShortEdges" - android:resizeableActivity="true" /> - </application> -</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml deleted file mode 100644 index 4708cfd48381..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:focusableInTouchMode="true" - android:background="@android:color/holo_green_light"> - <EditText android:id="@+id/plain_text_input" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:inputType="text"/> -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml deleted file mode 100644 index 45d5917f86d6..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_orange_light"> - - <TextView - android:id="@+id/NonResizeableTest" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:gravity="center_vertical|center_horizontal" - android:text="NonResizeableActivity" - android:textAppearance="?android:attr/textAppearanceLarge"/> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml deleted file mode 100644 index 8d59b567e59b..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/black"> - - <Button - android:id="@+id/button_send_notification" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_centerVertical="true" - android:text="Send Notification" /> -</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml deleted file mode 100644 index 5d94e5177dcc..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/holo_orange_light"> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml deleted file mode 100644 index 674bb70ad01e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:background="@android:color/holo_blue_light"> - - <TextView - android:id="@+id/SplitScreenTest" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:gravity="center_vertical|center_horizontal" - android:text="SecondaryActivity" - android:textAppearance="?android:attr/textAppearanceLarge"/> - -</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java deleted file mode 100644 index a2b580da5898..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.testapp; - -import android.content.ComponentName; - -public class Components { - public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp"; - - public static class SimpleActivity { - public static final String LABEL = "SimpleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SimpleActivity"); - } - - public static class FixedActivity { - public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation"; - public static final String LABEL = "FixedApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".FixedActivity"); - } - - public static class NonResizeableActivity { - public static final String LABEL = "NonResizeableApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".NonResizeableActivity"); - } - - public static class PipActivity { - // Test App > Pip Activity - public static final String LABEL = "PipApp"; - public static final String MENU_ACTION_NO_OP = "No-Op"; - public static final String MENU_ACTION_ON = "On"; - public static final String MENU_ACTION_OFF = "Off"; - public static final String MENU_ACTION_CLEAR = "Clear"; - - // Intent action that this activity dynamically registers to enter picture-in-picture - public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP"; - // Intent action that this activity dynamically registers to set requested orientation. - // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra. - public static final String ACTION_SET_REQUESTED_ORIENTATION = - PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION"; - - // Calls enterPictureInPicture() on creation - public static final String EXTRA_ENTER_PIP = "enter_pip"; - // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation} - public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation"; - // Adds a click listener to finish this activity when it is clicked - public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish"; - - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".PipActivity"); - } - - public static class ImeActivity { - public static final String LABEL = "ImeApp"; - public static final String ACTION_CLOSE_IME = - PACKAGE_NAME + ".action.CLOSE_IME"; - public static final String ACTION_OPEN_IME = - PACKAGE_NAME + ".action.OPEN_IME"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".ImeActivity"); - } - - public static class SplitScreenActivity { - public static final String LABEL = "SplitScreenPrimaryApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SplitScreenActivity"); - } - - public static class SplitScreenSecondaryActivity { - public static final String LABEL = "SplitScreenSecondaryApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SplitScreenSecondaryActivity"); - } - - public static class SendNotificationActivity { - public static final String LABEL = "SendNotificationApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".SendNotificationActivity"); - } - - public static class LaunchBubbleActivity { - public static final String LABEL = "LaunchBubbleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".LaunchBubbleActivity"); - } - - public static class BubbleActivity { - public static final String LABEL = "BubbleApp"; - public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, - PACKAGE_NAME + ".BubbleActivity"); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java deleted file mode 100644 index 59c64a1345ab..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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. - */ - -package com.android.wm.shell.flicker.testapp; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; - -public class ImeActivity extends Activity { - private static final String ACTION_OPEN_IME = - "com.android.wm.shell.flicker.testapp.action.OPEN_IME"; - private static final String ACTION_CLOSE_IME = - "com.android.wm.shell.flicker.testapp.action.CLOSE_IME"; - - private InputMethodManager mImm; - private View mEditText; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); - setContentView(R.layout.activity_ime); - - mEditText = findViewById(R.id.plain_text_input); - mImm = getSystemService(InputMethodManager.class); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - final String action = intent.getAction(); - if (ACTION_OPEN_IME.equals(action)) { - mEditText.requestFocus(); - mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED); - } else if (ACTION_CLOSE_IME.equals(action)) { - mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); - mEditText.clearFocus(); - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java deleted file mode 100644 index 8020ef2270a0..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.testapp; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; - -public class SendNotificationActivity extends Activity { - private NotificationManager mNotificationManager; - private String mChannelId = "Channel id"; - private String mChannelName = "Channel name"; - private NotificationChannel mChannel; - private int mNotifyId = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_notification); - findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification); - - mChannel = new NotificationChannel(mChannelId, mChannelName, - NotificationManager.IMPORTANCE_DEFAULT); - mNotificationManager = getSystemService(NotificationManager.class); - mNotificationManager.createNotificationChannel(mChannel); - } - - private void sendNotification(View v) { - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, - new Intent(this, SendNotificationActivity.class), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - Notification notification = new Notification.Builder(this, mChannelId) - .setContentTitle("Notification App") - .setContentText("Notification content") - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_message) - .setContentIntent(pendingIntent) - .build(); - - mNotificationManager.notify(mNotifyId, notification); - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java deleted file mode 100644 index 5343c1893d4e..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -package com.android.wm.shell.flicker.testapp; - -import android.app.Activity; -import android.os.Bundle; -import android.view.WindowManager; - -public class SimpleActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); - setContentView(R.layout.activity_simple); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index a7234c1d3cb8..98b59126227c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -18,7 +18,9 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -27,6 +29,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.animation.Animator; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -76,4 +79,18 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim verify(mController).onAnimationFinished(mTransition); } + + @Test + public void testChangesBehindStartingWindow() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_BEHIND_STARTING_WINDOW); + info.addChange(embeddingChange); + final Animator animator = mAnimRunner.createAnimator( + info, mStartTransaction, mFinishTransaction, + () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + + // The animation should be empty when it is behind starting window. + assertEquals(0, animator.getDuration()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 577942505b13..c628f3994d8d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -32,13 +32,14 @@ import android.app.WindowConfiguration; import android.os.Handler; import android.os.IBinder; import android.testing.AndroidTestingRunner; +import android.window.DisplayAreaInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction.Change; import androidx.test.filters.SmallTest; -import com.android.wm.shell.RootDisplayAreaOrganizer; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; @@ -59,7 +60,7 @@ public class DesktopModeControllerTest extends ShellTestCase { @Mock private ShellTaskOrganizer mShellTaskOrganizer; @Mock - private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; + private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Mock private ShellExecutor mTestExecutor; @Mock @@ -75,7 +76,7 @@ public class DesktopModeControllerTest extends ShellTestCase { mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer, - mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions); + mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions); mShellInit.init(); } @@ -94,19 +95,19 @@ public class DesktopModeControllerTest extends ShellTestCase { when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks( mContext.getDisplayId())).thenReturn(taskWct); - // Create a fake WCT to simulate setting display windowing mode to freeform - WindowContainerTransaction displayWct = new WindowContainerTransaction(); + // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly MockToken displayMockToken = new MockToken(); - displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM); - when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(), - WINDOWING_MODE_FREEFORM)).thenReturn(displayWct); + DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken, + mContext.getDisplayId(), 0); + when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId())) + .thenReturn(displayAreaInfo); // The test mController.updateDesktopModeActive(true); ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( WindowContainerTransaction.class); - verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture()); + verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture()); // WCT should have 2 changes - clear task wm mode and set display wm mode WindowContainerTransaction wct = arg.getValue(); @@ -118,7 +119,7 @@ public class DesktopModeControllerTest extends ShellTestCase { assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); // Verify executed WCT has a change for setting display windowing mode to freeform - Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder()); + Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder()); assertThat(displayWmModeChange).isNotNull(); assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM); } @@ -139,19 +140,19 @@ public class DesktopModeControllerTest extends ShellTestCase { when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks( mContext.getDisplayId())).thenReturn(taskBoundsWct); - // Create a fake WCT to simulate setting display windowing mode to fullscreen - WindowContainerTransaction displayWct = new WindowContainerTransaction(); + // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly MockToken displayMockToken = new MockToken(); - displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN); - when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(), - WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct); + DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken, + mContext.getDisplayId(), 0); + when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId())) + .thenReturn(displayAreaInfo); // The test mController.updateDesktopModeActive(false); ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( WindowContainerTransaction.class); - verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture()); + verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture()); // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode WindowContainerTransaction wct = arg.getValue(); @@ -171,7 +172,7 @@ public class DesktopModeControllerTest extends ShellTestCase { .isTrue(); // Verify executed WCT has a change for setting display windowing mode to fullscreen - Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder()); + Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder()); assertThat(displayWmModeChange).isNotNull(); assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java new file mode 100644 index 000000000000..a88c83779f25 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.floating; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TaskViewTransitions; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.floating.views.FloatingTaskLayer; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +/** + * Tests for the floating tasks controller. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class FloatingTasksControllerTest extends ShellTestCase { + // Some behavior in the controller constructor is dependent on this so we can only + // validate if it's working for the real value for those things. + private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); + + @Mock private ShellInit mShellInit; + @Mock private ShellController mShellController; + @Mock private WindowManager mWindowManager; + @Mock private ShellTaskOrganizer mTaskOrganizer; + @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor; + + private FloatingTasksController mController; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + + WindowMetrics windowMetrics = mock(WindowMetrics.class); + WindowInsets windowInsets = mock(WindowInsets.class); + Insets insets = Insets.of(0, 0, 0, 0); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics); + when(windowMetrics.getWindowInsets()).thenReturn(windowInsets); + when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000)); + when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets); + + // For the purposes of this test, just run everything synchronously + ShellExecutor shellExecutor = new TestShellExecutor(); + when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor); + } + + @After + public void tearDown() { + if (mController != null) { + mController.removeTask(); + mController = null; + } + } + + private void setUpTabletConfig() { + Configuration config = mock(Configuration.class); + config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; + mController.setConfig(config); + } + + private void setUpPhoneConfig() { + Configuration config = mock(Configuration.class); + config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1; + mController.setConfig(config); + } + + private void createController() { + mController = new FloatingTasksController(mContext, + mShellInit, + mShellController, + mock(ShellCommandHandler.class), + Optional.empty(), + mWindowManager, + mTaskOrganizer, + mock(TaskViewTransitions.class), + mock(ShellExecutor.class), + mock(ShellExecutor.class), + mock(SyncTransactionQueue.class)); + spyOn(mController); + } + + // + // Shell specific + // + @Test + public void instantiateController_addInitCallback() { + if (FLOATING_TASKS_ACTUALLY_ENABLED) { + createController(); + setUpTabletConfig(); + + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + } + + @Test + public void instantiateController_doesntAddInitCallback() { + if (!FLOATING_TASKS_ACTUALLY_ENABLED) { + createController(); + + verify(mShellInit, never()).addInitCallback(any(), any()); + } + } + + @Test + public void onInit_registerConfigChangeListener() { + if (FLOATING_TASKS_ACTUALLY_ENABLED) { + createController(); + setUpTabletConfig(); + mController.onInit(); + + verify(mShellController, times(1)).addConfigurationChangeListener(any()); + } + } + + // + // Tests for floating layer, which is only available for tablets. + // + + @Test + public void testIsFloatingLayerAvailable_true() { + createController(); + setUpTabletConfig(); + assertThat(mController.isFloatingLayerAvailable()).isTrue(); + } + + @Test + public void testIsFloatingLayerAvailable_false() { + createController(); + setUpPhoneConfig(); + assertThat(mController.isFloatingLayerAvailable()).isFalse(); + } + + // + // Tests for floating tasks being enabled, guarded by sysprop flag. + // + + @Test + public void testIsFloatingTasksEnabled_true() { + createController(); + mController.setFloatingTasksEnabled(true); + setUpTabletConfig(); + assertThat(mController.isFloatingTasksEnabled()).isTrue(); + } + + @Test + public void testIsFloatingTasksEnabled_false() { + createController(); + mController.setFloatingTasksEnabled(false); + setUpTabletConfig(); + assertThat(mController.isFloatingTasksEnabled()).isFalse(); + } + + // + // Tests for behavior depending on flags + // + + @Test + public void testShowTaskIntent_enabled() { + createController(); + mController.setFloatingTasksEnabled(true); + setUpTabletConfig(); + + mController.showTask(mock(Intent.class)); + verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); + assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); + } + + @Test + public void testShowTaskIntent_notEnabled() { + createController(); + mController.setFloatingTasksEnabled(false); + setUpTabletConfig(); + + mController.showTask(mock(Intent.class)); + verify(mWindowManager, never()).addView(any(), any()); + } + + @Test + public void testRemoveTask() { + createController(); + mController.setFloatingTasksEnabled(true); + setUpTabletConfig(); + + mController.showTask(mock(Intent.class)); + verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); + assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); + + mController.removeTask(); + verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture()); + assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 9240abfbe47f..835087007b30 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -16,7 +16,10 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -28,11 +31,10 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -42,8 +44,10 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Bundle; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -115,7 +119,6 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, Optional.empty())); - doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt()); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); @@ -303,4 +306,16 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false)); } + + @Test + public void testAddActivityOptions_addsBackgroundActivitiesFlags() { + Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN, + SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */); + + assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class), + mMainStage.mRootTaskInfo.token); + assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)); + assertTrue(options.getBoolean( + KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION)); + } } diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index d06bbc61f6eb..980accae733b 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -516,6 +516,9 @@ public final class AudioFormat implements Parcelable { /** Output channel mask for 5.1 */ public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT); + /** Output channel mask for 6.1 + * Same as 5.1 with the addition of the back center channel */ + public static final int CHANNEL_OUT_6POINT1 = (CHANNEL_OUT_5POINT1 | CHANNEL_OUT_BACK_CENTER); /** @hide */ public static final int CHANNEL_OUT_5POINT1_SIDE = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java index 491a8ec2c130..87634aa37fea 100644 --- a/media/java/android/media/audio/common/AidlConversion.java +++ b/media/java/android/media/audio/common/AidlConversion.java @@ -355,13 +355,7 @@ public class AidlConversion { case AudioChannelLayout.LAYOUT_5POINT1POINT4: return AudioFormat.CHANNEL_OUT_5POINT1POINT4; case AudioChannelLayout.LAYOUT_6POINT1: - return AudioFormat.CHANNEL_OUT_FRONT_LEFT - | AudioFormat.CHANNEL_OUT_FRONT_RIGHT - | AudioFormat.CHANNEL_OUT_FRONT_CENTER - | AudioFormat.CHANNEL_OUT_LOW_FREQUENCY - | AudioFormat.CHANNEL_OUT_BACK_LEFT - | AudioFormat.CHANNEL_OUT_BACK_RIGHT - | AudioFormat.CHANNEL_OUT_BACK_CENTER; + return AudioFormat.CHANNEL_OUT_6POINT1; case AudioChannelLayout.LAYOUT_7POINT1: return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; case AudioChannelLayout.LAYOUT_7POINT1POINT2: diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 9eacc74843f9..7891ee6d5861 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -333,7 +333,11 @@ public final class PlaybackState implements Parcelable { @Override public String toString() { StringBuilder bob = new StringBuilder("PlaybackState {"); - bob.append("state=").append(mState); + bob.append("state=") + .append(getStringForStateInt(mState)) + .append("(") + .append(mState) + .append(")"); bob.append(", position=").append(mPosition); bob.append(", buffered position=").append(mBufferedPosition); bob.append(", speed=").append(mSpeed); @@ -533,6 +537,38 @@ public final class PlaybackState implements Parcelable { } }; + /** Returns a human readable string representation of the given int {@code state} */ + private static String getStringForStateInt(int state) { + switch (state) { + case STATE_NONE: + return "NONE"; + case STATE_STOPPED: + return "STOPPED"; + case STATE_PAUSED: + return "PAUSED"; + case STATE_PLAYING: + return "PLAYING"; + case STATE_FAST_FORWARDING: + return "FAST_FORWARDING"; + case STATE_REWINDING: + return "REWINDING"; + case STATE_BUFFERING: + return "BUFFERING"; + case STATE_ERROR: + return "ERROR"; + case STATE_CONNECTING: + return "CONNECTING"; + case STATE_SKIPPING_TO_PREVIOUS: + return "SKIPPING_TO_PREVIOUS"; + case STATE_SKIPPING_TO_NEXT: + return "SKIPPING_TO_NEXT"; + case STATE_SKIPPING_TO_QUEUE_ITEM: + return "SKIPPING_TO_QUEUE_ITEM"; + default: + return "UNKNOWN"; + } + } + /** * {@link PlaybackState.CustomAction CustomActions} can be used to extend the capabilities of * the standard transport controls by exposing app specific actions to diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp index ecb1d510f807..9868a148d998 100644 --- a/media/jni/android_media_MediaProfiles.cpp +++ b/media/jni/android_media_MediaProfiles.cpp @@ -255,21 +255,21 @@ android_media_MediaProfiles_native_get_camcorder_profiles(JNIEnv *env, jobject / jmethodID audioProfileConstructorMethodID = env->GetMethodID(audioProfileClazz, "<init>", "(IIIII)V"); - jobjectArray videoCodecs = (jobjectArray)env->NewObjectArray( - cp->getVideoCodecs().size(), videoProfileClazz, nullptr); + jobjectArray videoCodecs = nullptr; { - int i = 0; + auto isAdvancedCodec = [](const MediaProfiles::VideoCodec *vc) -> bool { + return ((vc->getBitDepth() != 8 + || vc->getChromaSubsampling() != CHROMA_SUBSAMPLING_YUV_420 + || vc->getHdrFormat() != HDR_FORMAT_NONE)); + }; + std::vector<jobject> codecVector; for (const MediaProfiles::VideoCodec *vc : cp->getVideoCodecs()) { + if (isAdvancedCodec(vc) && !static_cast<bool>(advanced)) { + continue; + } chroma_subsampling cs = vc->getChromaSubsampling(); int bitDepth = vc->getBitDepth(); hdr_format hdr = vc->getHdrFormat(); - - bool isAdvanced = - (bitDepth != 8 || cs != CHROMA_SUBSAMPLING_YUV_420 || hdr != HDR_FORMAT_NONE); - if (static_cast<bool>(advanced) && !isAdvanced) { - continue; - } - jobject videoCodec = env->NewObject(videoProfileClazz, videoProfileConstructorMethodID, vc->getCodec(), @@ -281,10 +281,17 @@ android_media_MediaProfiles_native_get_camcorder_profiles(JNIEnv *env, jobject / static_cast<int>(cs), bitDepth, static_cast<int>(hdr)); - env->SetObjectArrayElement(videoCodecs, i++, videoCodec); + + codecVector.push_back(videoCodec); } - } + videoCodecs = (jobjectArray)env->NewObjectArray(codecVector.size(), + videoProfileClazz, nullptr); + int i = 0; + for (jobject codecObj : codecVector) { + env->SetObjectArrayElement(videoCodecs, i++, codecObj); + } + } jobjectArray audioCodecs; if (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LIST_START && quality <= CAMCORDER_QUALITY_TIME_LAPSE_LIST_END) { diff --git a/native/android/OWNERS b/native/android/OWNERS index cfe973400c98..d41652f2ad17 100644 --- a/native/android/OWNERS +++ b/native/android/OWNERS @@ -23,3 +23,6 @@ per-file hardware_buffer_jni.cpp = file:/graphics/java/android/graphics/OWNERS per-file native_window_jni.cpp = file:/graphics/java/android/graphics/OWNERS per-file surface_control.cpp = file:/graphics/java/android/graphics/OWNERS per-file surface_texture.cpp = file:/graphics/java/android/graphics/OWNERS + +# Input +per-file input.cpp = file:/INPUT_OWNERS diff --git a/native/android/input.cpp b/native/android/input.cpp index a231d8f153e7..812db0f1c507 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -295,6 +295,8 @@ int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) { return AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE; case android::MotionClassification::DEEP_PRESS: return AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS; + case android::MotionClassification::TWO_FINGER_SWIPE: + return AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE; } } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 1f96617a1ede..cb0f22f974ad 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -238,6 +238,7 @@ LIBANDROID { ASurfaceControl_createFromWindow; # introduced=29 ASurfaceControl_acquire; # introduced=31 ASurfaceControl_release; # introduced=29 + ASurfaceControl_fromSurfaceControl; # introduced=34 ASurfaceTexture_acquireANativeWindow; # introduced=28 ASurfaceTexture_attachToGLContext; # introduced=28 ASurfaceTexture_detachFromGLContext; # introduced=28 @@ -255,6 +256,7 @@ LIBANDROID { ASurfaceTransaction_apply; # introduced=29 ASurfaceTransaction_create; # introduced=29 ASurfaceTransaction_delete; # introduced=29 + ASurfaceTransaction_fromTransaction; # introduced=34 ASurfaceTransaction_reparent; # introduced=29 ASurfaceTransaction_setBuffer; # introduced=29 ASurfaceTransaction_setBufferAlpha; # introduced=29 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 42f4406ce5e8..9e4d72671502 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -17,6 +17,8 @@ #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h> #include <android/native_window.h> #include <android/surface_control.h> +#include <android/surface_control_jni.h> +#include <android_runtime/android_view_SurfaceControl.h> #include <configstore/Utils.h> #include <gui/HdrMetadata.h> #include <gui/ISurfaceComposer.h> @@ -28,6 +30,8 @@ #include <ui/DynamicDisplayInfo.h> #include <utils/Timers.h> +#include <utility> + using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; using namespace android; @@ -134,6 +138,11 @@ void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { SurfaceControl_release(surfaceControl); } +ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) { + return reinterpret_cast<ASurfaceControl*>( + android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj)); +} + struct ASurfaceControlStats { std::variant<int64_t, sp<Fence>> acquireTimeOrFence; sp<Fence> previousReleaseFence; @@ -190,6 +199,11 @@ void ASurfaceTransaction_delete(ASurfaceTransaction* aSurfaceTransaction) { delete transaction; } +ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) { + return reinterpret_cast<ASurfaceTransaction*>( + android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj)); +} + void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) { CHECK_NOT_NULL(aSurfaceTransaction); diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml index 1644110bfe16..276127f6007d 100644 --- a/packages/CompanionDeviceManager/res/values-be/strings.xml +++ b/packages/CompanionDeviceManager/res/values-be/strings.xml @@ -25,7 +25,7 @@ <string name="permission_apps_summary" msgid="798718816711515431">"Трансліруйце змесціва праграм з вашага тэлефона"</string> <string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> мець доступ да гэтай інфармацыі з вашага тэлефона"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string> - <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на перадачу праграм плынню паміж вашымі прыладамі"</string> + <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> <string name="title_computer" msgid="4693714143506569253">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> мець доступ да гэтай інфармацыі з вашага тэлефона"</string> diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml index 28aaa87756b2..83d0e02a5566 100644 --- a/packages/CompanionDeviceManager/res/values-eu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml @@ -28,7 +28,7 @@ <string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> - <string name="title_computer" msgid="4693714143506569253">"Eman informazio hori telefonotik hartzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string> + <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau atzitzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string> <string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string> diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml index a2c628ffbc23..59f4f8971881 100644 --- a/packages/CompanionDeviceManager/res/values-is/strings.xml +++ b/packages/CompanionDeviceManager/res/values-is/strings.xml @@ -25,13 +25,13 @@ <string name="permission_apps_summary" msgid="798718816711515431">"Streymdu forritum símans"</string> <string name="title_app_streaming" msgid="2270331024626446950">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aðgang að þessum upplýsingum úr símanum þínum"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string> - <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild fyrir straumspilun forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string> + <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild til straumspilunar forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> <string name="title_computer" msgid="4693714143506569253">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aðgang að þessum upplýsingum úr símanum þínum"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string> - <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði skilaboð og myndir"</string> + <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string> <string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string> <string name="permission_storage_summary" msgid="3918240895519506417"></string> <string name="helper_title_computer" msgid="4671071173916176037">"Þjónusta Google Play"</string> diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml index 3b2ce6d072d2..a3cd4ee3df28 100644 --- a/packages/CompanionDeviceManager/res/values-ja/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml @@ -28,7 +28,7 @@ <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> - <string name="title_computer" msgid="4693714143506569253">"このスマートフォンからの情報へのアクセスを <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> に許可"</string> + <string name="title_computer" msgid="4693714143506569253">"スマートフォンのこの情報へのアクセスを <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> に許可"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"通知"</string> <string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string> diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml index bc140a2d3a48..ead203721afe 100644 --- a/packages/CompanionDeviceManager/res/values-ky/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml @@ -25,14 +25,14 @@ <string name="permission_apps_summary" msgid="798718816711515431">"Телефондогу колдонмолорду алып ойнотуу"</string> <string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string> - <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду тышкы экранга чыгарууга уруксат сурап жатат"</string> + <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду өткөрүүгө уруксат сурап жатат"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> <string name="title_computer" msgid="4693714143506569253">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string> <string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string> - <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиа"</string> + <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string> <string name="permission_storage_summary" msgid="3918240895519506417"></string> <string name="helper_title_computer" msgid="4671071173916176037">"Google Play кызматтары"</string> <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан телефондогу сүрөттөрдү, медиа файлдарды жана билдирмелерди колдонууга уруксат сурап жатат"</string> diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml index 2039ee04fcca..9c7cc3c988b7 100644 --- a/packages/CompanionDeviceManager/res/values-nl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml @@ -22,7 +22,7 @@ <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="summary_watch" msgid="3002344206574997652">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string> <string name="permission_apps" msgid="6142133265286656158">"Apps"</string> - <string name="permission_apps_summary" msgid="798718816711515431">"De apps van je telefoon streamen"</string> + <string name="permission_apps_summary" msgid="798718816711515431">"Stream de apps van je telefoon"</string> <string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> toegang geven tot deze informatie op je telefoon"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device-services"</string> <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string> diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml index 1d87de917dcb..e43bed5cc11e 100644 --- a/packages/CompanionDeviceManager/res/values-ro/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml @@ -39,7 +39,7 @@ <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string> <string name="summary_generic" msgid="2346762210105903720"></string> <string name="consent_yes" msgid="8344487259618762872">"Permite"</string> - <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string> + <string name="consent_no" msgid="2640796915611404382">"Nu permite"</string> <string name="consent_back" msgid="2560683030046918882">"Înapoi"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Oferiți aplicațiilor de pe <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> aceleași permisiuni ca pe <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> <string name="permission_sync_summary" msgid="4866838188678457084">"<p>Aici pot fi incluse accesul la microfon, la camera foto, la locație și alte permisiuni de accesare a informațiilor sensibile de pe <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>Puteți modifica oricând aceste permisiuni din Setările de pe <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string> diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml index 9537dfdb3e17..ff19fa577448 100644 --- a/packages/CompanionDeviceManager/res/values-sk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml @@ -25,7 +25,7 @@ <string name="permission_apps_summary" msgid="798718816711515431">"Streamovať aplikácie telefónu"</string> <string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> prístup k týmto informáciám z vášho telefónu"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string> - <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string> + <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> <string name="title_computer" msgid="4693714143506569253">"Povoľte aplikácii <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> prístup k týmto informáciám z vášho telefónu"</string> diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml index 9cb5f57eb038..812b4df347b8 100644 --- a/packages/CompanionDeviceManager/res/values-sw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml @@ -25,7 +25,7 @@ <string name="permission_apps_summary" msgid="798718816711515431">"Tiririsha programu za simu yako"</string> <string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie maelezo haya kutoka kwenye simu yako"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string> - <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string> + <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> <string name="title_computer" msgid="4693714143506569253">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie maelezo haya kutoka kwenye simu yako"</string> @@ -35,7 +35,7 @@ <string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string> <string name="permission_storage_summary" msgid="3918240895519506417"></string> <string name="helper_title_computer" msgid="4671071173916176037">"Huduma za Google Play"</string> - <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string> + <string name="helper_summary_computer" msgid="9050724687678157852">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string> <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string> <string name="summary_generic" msgid="2346762210105903720"></string> <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string> diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml index 8a3df03d3050..c3187963086b 100644 --- a/packages/CompanionDeviceManager/res/values-te/strings.xml +++ b/packages/CompanionDeviceManager/res/values-te/strings.xml @@ -31,7 +31,7 @@ <string name="title_computer" msgid="4693714143506569253">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> యాప్ను అనుమతించండి"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్లు"</string> - <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్లు, మెసేజ్లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్లను చదవగలరు"</string> + <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్లు, మెసేజ్లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్లను చదవగలదు"</string> <string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string> <string name="permission_storage_summary" msgid="3918240895519506417"></string> <string name="helper_title_computer" msgid="4671071173916176037">"Google Play సర్వీసులు"</string> diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml index 074049882b5c..65b2ba576d41 100644 --- a/packages/CompanionDeviceManager/res/values-ur/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml @@ -23,12 +23,12 @@ <string name="summary_watch" msgid="3002344206574997652">"آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے اس ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string> <string name="permission_apps" msgid="6142133265286656158">"ایپس"</string> <string name="permission_apps_summary" msgid="798718816711515431">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</string> - <string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کو اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی اجازت دیں"</string> + <string name="title_app_streaming" msgid="2270331024626446950">"اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کو اجازت دیں"</string> <string name="helper_title_app_streaming" msgid="4151687003439969765">"کراس ڈیوائس سروسز"</string> <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string> <string name="title_automotive_projection" msgid="3296005598978412847"></string> <string name="summary_automotive_projection" msgid="8683801274662496164"></string> - <string name="title_computer" msgid="4693714143506569253">"اپنے فون سے اس معلومات تک رسائی حاصل Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کرنے کی اجازت دیں"</string> + <string name="title_computer" msgid="4693714143506569253">"اپنے فون سے اس معلومات تک رسائی حاصل کرنے کی <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> کو اجازت دیں"</string> <string name="summary_computer" msgid="3798467601598297062"></string> <string name="permission_notification" msgid="693762568127741203">"اطلاعات"</string> <string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string> diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp new file mode 100644 index 000000000000..51943fffb36d --- /dev/null +++ b/packages/CredentialManager/Android.bp @@ -0,0 +1,35 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "CredentialManager", + defaults: ["platform_app_defaults"], + certificate: "platform", + srcs: ["src/**/*.kt"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.activity_activity-compose", + "androidx.appcompat_appcompat", + "androidx.compose.material_material", + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui", + "androidx.compose.ui_ui-tooling", + "androidx.core_core-ktx", + "androidx.lifecycle_lifecycle-extensions", + "androidx.lifecycle_lifecycle-livedata", + "androidx.lifecycle_lifecycle-runtime-ktx", + "androidx.lifecycle_lifecycle-viewmodel-compose", + "androidx.recyclerview_recyclerview", + ], + + platform_apis: true, + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml new file mode 100644 index 000000000000..586ef86f26f6 --- /dev/null +++ b/packages/CredentialManager/AndroidManifest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (c) 2017 Google Inc. + * + * 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.credentialmanager"> + + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> + <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/> + + <application + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.CredentialSelector"> + + <activity + android:name=".CredentialSelectorActivity" + android:exported="true" + android:label="@string/app_name" + android:launchMode="singleInstance" + android:noHistory="true" + android:excludeFromRecents="true" + android:theme="@style/Theme.CredentialSelector"> + </activity> + </application> + +</manifest> diff --git a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml index 966abaff2074..966abaff2074 100644 --- a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml +++ b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml diff --git a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml b/packages/CredentialManager/res/drawable/ic_launcher_background.xml index 61bb79edb709..61bb79edb709 100644 --- a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml +++ b/packages/CredentialManager/res/drawable/ic_launcher_background.xml diff --git a/packages/CredentialManager/res/drawable/ic_passkey.xml b/packages/CredentialManager/res/drawable/ic_passkey.xml new file mode 100644 index 000000000000..041a32164073 --- /dev/null +++ b/packages/CredentialManager/res/drawable/ic_passkey.xml @@ -0,0 +1,16 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="28dp" + android:height="24dp" + android:viewportWidth="28" + android:viewportHeight="24"> + <path + android:pathData="M27.453,13.253C27.453,14.952 26.424,16.411 24.955,17.041L26.21,18.295L24.839,19.666L26.21,21.037L23.305,23.942L22.012,22.65L22.012,17.156C20.385,16.605 19.213,15.066 19.213,13.253C19.213,10.977 21.058,9.133 23.333,9.133C25.609,9.133 27.453,10.977 27.453,13.253ZM25.47,13.254C25.47,14.434 24.514,15.39 23.334,15.39C22.154,15.39 21.197,14.434 21.197,13.254C21.197,12.074 22.154,11.118 23.334,11.118C24.514,11.118 25.47,12.074 25.47,13.254Z" + android:fillColor="#00639B" + android:fillType="evenOdd"/> + <path + android:pathData="M17.85,5.768C17.85,8.953 15.268,11.536 12.083,11.536C8.897,11.536 6.315,8.953 6.315,5.768C6.315,2.582 8.897,0 12.083,0C15.268,0 17.85,2.582 17.85,5.768Z" + android:fillColor="#00639B"/> + <path + android:pathData="M0.547,20.15C0.547,16.32 8.23,14.382 12.083,14.382C13.59,14.382 15.684,14.679 17.674,15.269C18.116,16.454 18.952,17.447 20.022,18.089V23.071H0.547V20.15Z" + android:fillColor="#00639B"/> +</vector> diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml index 03eed2533da2..03eed2533da2 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml index 03eed2533da2..03eed2533da2 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp Binary files differindex c209e78ecd37..c209e78ecd37 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp +++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp Binary files differindex b2dfe3d1ba5c..b2dfe3d1ba5c 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp +++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp Binary files differindex 4f0f1d64e58b..4f0f1d64e58b 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp +++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp Binary files differindex 62b611da0816..62b611da0816 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp +++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp Binary files differindex 948a3070fe34..948a3070fe34 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp +++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differindex 1b9a6956b3ac..1b9a6956b3ac 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp +++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp Binary files differindex 28d4b77f9f03..28d4b77f9f03 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp +++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differindex 9287f5083623..9287f5083623 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp +++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differindex aa7d6427e6fa..aa7d6427e6fa 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp +++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differindex 9126ae37cbc3..9126ae37cbc3 100644 --- a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp +++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml new file mode 100644 index 000000000000..09837df62f44 --- /dev/null +++ b/packages/CredentialManager/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="purple_200">#FFBB86FC</color> + <color name="purple_500">#FF6200EE</color> + <color name="purple_700">#FF3700B3</color> + <color name="teal_200">#FF03DAC5</color> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml new file mode 100644 index 000000000000..2901705d5836 --- /dev/null +++ b/packages/CredentialManager/res/values/strings.xml @@ -0,0 +1,13 @@ +<resources> + <string name="app_name">CredentialManager</string> + <string name="string_cancel">Cancel</string> + <string name="string_continue">Continue</string> + <string name="string_more_options">More options</string> + <string name="string_no_thanks">No thanks</string> + <string name="passkey_creation_intro_title">A simple way to sign in safely</string> + <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string> + <string name="choose_provider_title">Choose your default provider</string> + <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string> + <string name="choose_create_option_title">Create a passkey at</string> + <string name="choose_sign_in_title">Use saved sign in</string> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml new file mode 100644 index 000000000000..feec74608000 --- /dev/null +++ b/packages/CredentialManager/res/values/themes.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material"> + <item name="android:statusBarColor">@color/purple_700</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:colorBackgroundCacheHint">@null</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/xml/backup_rules.xml b/packages/CredentialManager/res/xml/backup_rules.xml new file mode 100644 index 000000000000..9b42d90d94bb --- /dev/null +++ b/packages/CredentialManager/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample backup rules file; uncomment and customize as necessary. + See https://developer.android.com/guide/topics/data/autobackup + for details. + Note: This file is ignored for devices older that API 31 + See https://developer.android.com/about/versions/12/backup-restore +--> +<full-backup-content> + <!-- + <include domain="sharedpref" path="."/> + <exclude domain="sharedpref" path="device.xml"/> +--> +</full-backup-content>
\ No newline at end of file diff --git a/packages/CredentialManager/res/xml/data_extraction_rules.xml b/packages/CredentialManager/res/xml/data_extraction_rules.xml new file mode 100644 index 000000000000..c6c3bb05a956 --- /dev/null +++ b/packages/CredentialManager/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample data extraction rules file; uncomment and customize as necessary. + See https://developer.android.com/about/versions/12/backup-restore#xml-changes + for details. +--> +<data-extraction-rules> + <cloud-backup> + <!-- TODO: Use <include> and <exclude> to control what is backed up. + <include .../> + <exclude .../> + --> + </cloud-backup> + <!-- + <device-transfer> + <include .../> + <exclude .../> + </device-transfer> + --> +</data-extraction-rules>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt new file mode 100644 index 000000000000..59186336056e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -0,0 +1,151 @@ +package com.android.credentialmanager + +import android.content.Context +import com.android.credentialmanager.createflow.CreateOptionInfo +import com.android.credentialmanager.createflow.CreatePasskeyUiState +import com.android.credentialmanager.createflow.CreateScreenState +import com.android.credentialmanager.createflow.ProviderInfo +import com.android.credentialmanager.getflow.CredentialOptionInfo +import com.android.credentialmanager.getflow.GetCredentialUiState +import com.android.credentialmanager.getflow.GetScreenState + +// Consider repo per screen, similar to view model? +class CredentialManagerRepo( + private val context: Context +) { + private fun getCredentialProviderList(): + List<com.android.credentialmanager.getflow.ProviderInfo> { + return listOf( + com.android.credentialmanager.getflow.ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Google Password Manager", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + credentialOptions = listOf( + CredentialOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@gmail.com", + id = "id-1", + ), + CredentialOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett Work", + subtitle = "elisa.beckett.work@google.com", + id = "id-2", + ), + ) + ), + com.android.credentialmanager.getflow.ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Lastpass", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + credentialOptions = listOf( + CredentialOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@lastpass.com", + id = "id-1", + ), + ) + ), + com.android.credentialmanager.getflow.ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Dashlane", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + credentialOptions = listOf( + CredentialOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@dashlane.com", + id = "id-1", + ), + ) + ), + ) + } + + private fun createCredentialProviderList(): List<ProviderInfo> { + return listOf( + ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Google Password Manager", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + createOptions = listOf( + CreateOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@gmail.com", + id = "id-1", + ), + CreateOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett Work", + subtitle = "elisa.beckett.work@google.com", + id = "id-2", + ), + ) + ), + ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Lastpass", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + createOptions = listOf( + CreateOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@lastpass.com", + id = "id-1", + ), + ) + ), + ProviderInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + name = "Dashlane", + appDomainName = "tribank.us", + credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, + createOptions = listOf( + CreateOptionInfo( + icon = context.getDrawable(R.drawable.ic_passkey)!!, + title = "Elisa Backett", + subtitle = "elisa.beckett@dashlane.com", + id = "id-1", + ), + ) + ), + ) + } + + fun getCredentialInitialUiState(): GetCredentialUiState { + val providerList = getCredentialProviderList() + return GetCredentialUiState( + providerList, + GetScreenState.CREDENTIAL_SELECTION, + providerList.first() + ) + } + + fun createPasskeyInitialUiState(): CreatePasskeyUiState { + val providerList = createCredentialProviderList() + return CreatePasskeyUiState( + providers = providerList, + currentScreenState = CreateScreenState.PASSKEY_INTRO, + ) + } + + companion object { + lateinit var repo: CredentialManagerRepo + + fun setup(context: Context) { + repo = CredentialManagerRepo(context) + } + + fun getInstance(): CredentialManagerRepo { + return repo + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt new file mode 100644 index 000000000000..5cd6a13a377a --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -0,0 +1,52 @@ +package com.android.credentialmanager + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.Composable +import com.android.credentialmanager.common.DialogType +import com.android.credentialmanager.createflow.CreatePasskeyScreen +import com.android.credentialmanager.getflow.GetCredentialScreen +import com.android.credentialmanager.ui.theme.CredentialSelectorTheme + +@ExperimentalMaterialApi +class CredentialSelectorActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + CredentialManagerRepo.setup(this) + val startDestination = intent.extras?.getString( + "start_destination", + "CREATE_PASSKEY" + ) ?: "CREATE_PASSKEY" + + setContent { + CredentialSelectorTheme { + CredentialManagerBottomSheet(startDestination) + } + } + } + + @ExperimentalMaterialApi + @Composable + fun CredentialManagerBottomSheet(operationType: String) { + val dialogType = DialogType.toDialogType(operationType) + when (dialogType) { + DialogType.CREATE_PASSKEY -> { + CreatePasskeyScreen(cancelActivity = onCancel) + } + DialogType.GET_CREDENTIALS -> { + GetCredentialScreen(cancelActivity = onCancel) + } + else -> { + Log.w("AccountSelector", "Unknown type, not rendering any UI") + this.finish() + } + } + } + + private val onCancel = { + this@CredentialSelectorActivity.finish() + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt new file mode 100644 index 000000000000..8bb80a1b02fd --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt @@ -0,0 +1,18 @@ +package com.android.credentialmanager.common + +enum class DialogType { + CREATE_PASSKEY, + GET_CREDENTIALS, + CREATE_PASSWORD, + UNKNOWN; + + companion object { + fun toDialogType(value: String): DialogType { + return try { + valueOf(value) + } catch (e: IllegalArgumentException) { + UNKNOWN + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt new file mode 100644 index 000000000000..5aa1e9ba6183 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -0,0 +1,25 @@ +package com.android.credentialmanager.createflow + +import android.graphics.drawable.Drawable + +data class ProviderInfo( + val icon: Drawable, + val name: String, + val appDomainName: String, + val credentialTypeIcon: Drawable, + val createOptions: List<CreateOptionInfo>, +) + +data class CreateOptionInfo( + val icon: Drawable, + val title: String, + val subtitle: String, + val id: String, +) + +/** The name of the current screen. */ +enum class CreateScreenState { + PASSKEY_INTRO, + PROVIDER_SELECTION, + CREATION_OPTION_SELECTION, +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt new file mode 100644 index 000000000000..60a8e4b24d57 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -0,0 +1,398 @@ +package com.android.credentialmanager.createflow + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Button +import androidx.compose.material.ButtonColors +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Card +import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults +import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Text +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.viewmodel.compose.viewModel +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.theme.Grey100 +import com.android.credentialmanager.ui.theme.Shapes +import com.android.credentialmanager.ui.theme.Typography +import com.android.credentialmanager.ui.theme.lightBackgroundColor +import com.android.credentialmanager.ui.theme.lightColorAccentSecondary +import com.android.credentialmanager.ui.theme.lightSurface1 + +@ExperimentalMaterialApi +@Composable +fun CreatePasskeyScreen( + viewModel: CreatePasskeyViewModel = viewModel(), + cancelActivity: () -> Unit, +) { + val state = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Expanded, + skipHalfExpanded = true + ) + ModalBottomSheetLayout( + sheetState = state, + sheetContent = { + val uiState = viewModel.uiState + when (uiState.currentScreenState) { + CreateScreenState.PASSKEY_INTRO -> ConfirmationCard( + onConfirm = {viewModel.onConfirmIntro()}, + onCancel = cancelActivity, + ) + CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard( + providerList = uiState.providers, + onCancel = cancelActivity, + onProviderSelected = {viewModel.onProviderSelected(it)} + ) + CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard( + providerInfo = uiState.selectedProvider!!, + onOptionSelected = {viewModel.onCreateOptionSelected(it)}, + onCancel = cancelActivity, + multiProvider = uiState.providers.size > 1, + onMoreOptionSelected = {viewModel.onMoreOptionSelected()} + ) + } + }, + scrimColor = Color.Transparent, + sheetShape = Shapes.medium, + ) {} + LaunchedEffect(state.currentValue) { + when (state.currentValue) { + ModalBottomSheetValue.Hidden -> { + cancelActivity() + } + } + } +} + +@Composable +fun ConfirmationCard( + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + Card( + backgroundColor = lightBackgroundColor, + ) { + Column() { + Icon( + painter = painterResource(R.drawable.ic_passkey), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp) + ) + Text( + text = stringResource(R.string.passkey_creation_intro_title), + style = Typography.subtitle1, + modifier = Modifier + .padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally) + ) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Text( + text = stringResource(R.string.passkey_creation_intro_body), + style = Typography.body1, + modifier = Modifier.padding(horizontal = 28.dp) + ) + Divider( + thickness = 48.dp, + color = Color.Transparent + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + ) { + CancelButton( + stringResource(R.string.string_cancel), + onclick = onCancel + ) + ConfirmButton( + stringResource(R.string.string_continue), + onclick = onConfirm + ) + } + Divider( + thickness = 18.dp, + color = Color.Transparent, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun ProviderSelectionCard( + providerList: List<ProviderInfo>, + onProviderSelected: (String) -> Unit, + onCancel: () -> Unit +) { + Card( + backgroundColor = lightBackgroundColor, + ) { + Column() { + Text( + text = stringResource(R.string.choose_provider_title), + style = Typography.subtitle1, + modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally) + ) + Text( + text = stringResource(R.string.choose_provider_body), + style = Typography.body1, + modifier = Modifier.padding(horizontal = 28.dp) + ) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Card( + shape = Shapes.medium, + modifier = Modifier + .padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally) + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + providerList.forEach { + item { + ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected) + } + } + } + } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Row( + horizontalArrangement = Arrangement.Start, + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + ) { + CancelButton(stringResource(R.string.string_cancel), onCancel) + } + Divider( + thickness = 18.dp, + color = Color.Transparent, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) { + Chip( + modifier = Modifier.fillMaxWidth(), + onClick = {onProviderSelected(providerInfo.name)}, + leadingIcon = { + Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), + bitmap = providerInfo.icon.toBitmap().asImageBitmap(), + // painter = painterResource(R.drawable.ic_passkey), + // TODO: add description. + contentDescription = "") + }, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Text( + text = providerInfo.name, + style = Typography.button, + modifier = Modifier.padding(vertical = 18.dp) + ) + } +} + +@Composable +fun CancelButton(text: String, onclick: () -> Unit) { + val colors = ButtonDefaults.buttonColors( + backgroundColor = lightBackgroundColor + ) + NavigationButton( + border = BorderStroke(1.dp, lightSurface1), + colors = colors, + text = text, + onclick = onclick) +} + +@Composable +fun ConfirmButton(text: String, onclick: () -> Unit) { + val colors = ButtonDefaults.buttonColors( + backgroundColor = lightColorAccentSecondary + ) + NavigationButton( + colors = colors, + text = text, + onclick = onclick) +} + +@Composable +fun NavigationButton( + border: BorderStroke? = null, + colors: ButtonColors, + text: String, + onclick: () -> Unit +) { + Button( + onClick = onclick, + shape = Shapes.small, + colors = colors, + border = border + ) { + Text(text = text, style = Typography.button) + } +} + +@ExperimentalMaterialApi +@Composable +fun CreationSelectionCard( + providerInfo: ProviderInfo, + onOptionSelected: (String) -> Unit, + onCancel: () -> Unit, + multiProvider: Boolean, + onMoreOptionSelected: () -> Unit, +) { + Card( + backgroundColor = lightBackgroundColor, + ) { + Column() { + Icon( + bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp) + ) + Text( + text = "${stringResource(R.string.choose_create_option_title)} ${providerInfo.name}", + style = Typography.subtitle1, + modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally) + ) + Text( + text = providerInfo.appDomainName, + style = Typography.body2, + modifier = Modifier.padding(horizontal = 28.dp) + ) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Card( + shape = Shapes.medium, + modifier = Modifier + .padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally) + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + providerInfo.createOptions.forEach { + item { + CreateOptionRow(createOptionInfo = it, onOptionSelected = onOptionSelected) + } + } + if (multiProvider) { + item { + MoreOptionRow(onSelect = onMoreOptionSelected) + } + } + } + } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Row( + horizontalArrangement = Arrangement.Start, + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + ) { + CancelButton(stringResource(R.string.string_cancel), onCancel) + } + Divider( + thickness = 18.dp, + color = Color.Transparent, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) { + Chip( + modifier = Modifier.fillMaxWidth(), + onClick = {onOptionSelected(createOptionInfo.id)}, + leadingIcon = { + Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), + bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(), + // painter = painterResource(R.drawable.ic_passkey), + // TODO: add description. + contentDescription = "") + }, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Column() { + Text( + text = createOptionInfo.title, + style = Typography.h6, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = createOptionInfo.subtitle, + style = Typography.body2, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun MoreOptionRow(onSelect: () -> Unit) { + Chip( + modifier = Modifier.fillMaxWidth().height(52.dp), + onClick = onSelect, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Text( + text = stringResource(R.string.string_more_options), + style = Typography.h6, + ) + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt new file mode 100644 index 000000000000..e42016d59b0b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -0,0 +1,58 @@ +package com.android.credentialmanager.createflow + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.android.credentialmanager.CredentialManagerRepo + +data class CreatePasskeyUiState( + val providers: List<ProviderInfo>, + val currentScreenState: CreateScreenState, + val selectedProvider: ProviderInfo? = null, +) + +class CreatePasskeyViewModel( + credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance() +) : ViewModel() { + + var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState()) + private set + + fun onConfirmIntro() { + if (uiState.providers.size > 1) { + uiState = uiState.copy( + currentScreenState = CreateScreenState.PROVIDER_SELECTION + ) + } else if (uiState.providers.size == 1){ + uiState = uiState.copy( + currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION, + selectedProvider = uiState.providers.first() + ) + } else { + throw java.lang.IllegalStateException("Empty provider list.") + } + } + + fun onProviderSelected(providerName: String) { + uiState = uiState.copy( + currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION, + selectedProvider = getProviderInfoByName(providerName) + ) + } + + fun onCreateOptionSelected(createOptionId: String) { + Log.d("Account Selector", "Option selected for creation: $createOptionId") + } + + fun getProviderInfoByName(providerName: String): ProviderInfo { + return uiState.providers.single { + it.name.equals(providerName) + } + } + + fun onMoreOptionSelected() { + Log.d("Account Selector", "On more option selected") + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt new file mode 100644 index 000000000000..4b957e84fc2b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -0,0 +1,203 @@ +package com.android.credentialmanager.getflow + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Card +import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults +import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Text +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.viewmodel.compose.viewModel +import com.android.credentialmanager.R +import com.android.credentialmanager.createflow.CancelButton +import com.android.credentialmanager.ui.theme.Grey100 +import com.android.credentialmanager.ui.theme.Shapes +import com.android.credentialmanager.ui.theme.Typography +import com.android.credentialmanager.ui.theme.lightBackgroundColor + +@ExperimentalMaterialApi +@Composable +fun GetCredentialScreen( + viewModel: GetCredentialViewModel = viewModel(), + cancelActivity: () -> Unit, +) { + val state = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Expanded, + skipHalfExpanded = true + ) + ModalBottomSheetLayout( + sheetState = state, + sheetContent = { + val uiState = viewModel.uiState + when (uiState.currentScreenState) { + GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard( + providerInfo = uiState.selectedProvider!!, + onCancel = cancelActivity, + onOptionSelected = {viewModel.onCredentailSelected(it)}, + multiProvider = uiState.providers.size > 1, + onMoreOptionSelected = {viewModel.onMoreOptionSelected()}, + ) + } + }, + scrimColor = Color.Transparent, + sheetShape = Shapes.medium, + ) {} + LaunchedEffect(state.currentValue) { + when (state.currentValue) { + ModalBottomSheetValue.Hidden -> { + cancelActivity() + } + } + } +} + +@ExperimentalMaterialApi +@Composable +fun CredentialSelectionCard( + providerInfo: ProviderInfo, + onOptionSelected: (String) -> Unit, + onCancel: () -> Unit, + multiProvider: Boolean, + onMoreOptionSelected: () -> Unit, +) { + Card( + backgroundColor = lightBackgroundColor, + ) { + Column() { + Icon( + bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp) + ) + Text( + text = stringResource(R.string.choose_sign_in_title), + style = Typography.subtitle1, + modifier = Modifier + .padding(all = 24.dp) + .align(alignment = Alignment.CenterHorizontally) + ) + Text( + text = providerInfo.appDomainName, + style = Typography.body2, + modifier = Modifier.padding(horizontal = 28.dp) + ) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Card( + shape = Shapes.medium, + modifier = Modifier + .padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally) + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + providerInfo.credentialOptions.forEach { + item { + CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected) + } + } + if (multiProvider) { + item { + MoreOptionRow(onSelect = onMoreOptionSelected) + } + } + } + } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Row( + horizontalArrangement = Arrangement.Start, + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + ) { + CancelButton(stringResource(R.string.string_no_thanks), onCancel) + } + Divider( + thickness = 18.dp, + color = Color.Transparent, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun CredentialOptionRow( + credentialOptionInfo: CredentialOptionInfo, + onOptionSelected: (String) -> Unit +) { + Chip( + modifier = Modifier.fillMaxWidth(), + onClick = {onOptionSelected(credentialOptionInfo.id)}, + leadingIcon = { + Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), + bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(), + // TODO: add description. + contentDescription = "") + }, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Column() { + Text( + text = credentialOptionInfo.title, + style = Typography.h6, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = credentialOptionInfo.subtitle, + style = Typography.body2, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun MoreOptionRow(onSelect: () -> Unit) { + Chip( + modifier = Modifier.fillMaxWidth().height(52.dp), + onClick = onSelect, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Text( + text = stringResource(R.string.string_more_options), + style = Typography.h6, + ) + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt new file mode 100644 index 000000000000..06bcd7fedc6f --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -0,0 +1,30 @@ +package com.android.credentialmanager.getflow + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.android.credentialmanager.CredentialManagerRepo + +data class GetCredentialUiState( + val providers: List<ProviderInfo>, + val currentScreenState: GetScreenState, + val selectedProvider: ProviderInfo? = null, +) + +class GetCredentialViewModel( + credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance() +) : ViewModel() { + + var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState()) + private set + + fun onCredentailSelected(credentialId: String) { + Log.d("Account Selector", "credential selected: $credentialId") + } + + fun onMoreOptionSelected() { + Log.d("Account Selector", "More Option selected") + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt new file mode 100644 index 000000000000..867e9c2acc63 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -0,0 +1,23 @@ +package com.android.credentialmanager.getflow + +import android.graphics.drawable.Drawable + +data class ProviderInfo( + val icon: Drawable, + val name: String, + val appDomainName: String, + val credentialTypeIcon: Drawable, + val credentialOptions: List<CredentialOptionInfo>, +) + +data class CredentialOptionInfo( + val icon: Drawable, + val title: String, + val subtitle: String, + val id: String, +) + +/** The name of the current screen. */ +enum class GetScreenState { + CREDENTIAL_SELECTION, +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt new file mode 100644 index 000000000000..abb4bfbf915e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt @@ -0,0 +1,14 @@ +package com.android.credentialmanager.ui.theme + +import androidx.compose.ui.graphics.Color + +val Grey100 = Color(0xFFF1F3F4) +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) +val lightColorAccentSecondary = Color(0xFFC2E7FF) +val lightBackgroundColor = Color(0xFFF0F0F0) +val lightSurface1 = Color(0xFF6991D6) +val textColorSecondary = Color(0xFF40484B) +val textColorPrimary = Color(0xFF191C1D) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt new file mode 100644 index 000000000000..cba86585ee59 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.android.credentialmanager.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(100.dp), + medium = RoundedCornerShape(20.dp), + large = RoundedCornerShape(0.dp) +) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt new file mode 100644 index 000000000000..a9d20ae9c42e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt @@ -0,0 +1,47 @@ +package com.android.credentialmanager.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun CredentialSelectorTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt new file mode 100644 index 000000000000..d8fb01c17f95 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt @@ -0,0 +1,56 @@ +package com.android.credentialmanager.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + subtitle1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 24.sp, + lineHeight = 32.sp, + ), + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 20.sp, + ), + body2 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 20.sp, + color = textColorSecondary + ), + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + ), + h6 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp, + color = textColorPrimary + ), + + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) diff --git a/packages/DynamicSystemInstallationService/res/values-ro/strings.xml b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml index 27b88db6eef4..22a46d5ec748 100644 --- a/packages/DynamicSystemInstallationService/res/values-ro/strings.xml +++ b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml @@ -1,16 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="keyguard_description" msgid="8582605799129954556">"Introduceți parola și accesați Actualizările de sistem dinamice"</string> + <string name="keyguard_description" msgid="8582605799129954556">"Introdu parola și accesați Actualizările de sistem dinamice"</string> <string name="notification_install_completed" msgid="6252047868415172643">"Sistemul dinamic este pregătit. Ca să începeți să-l folosiți, reporniți dispozitivul."</string> <string name="notification_install_inprogress" msgid="7383334330065065017">"Se instalează"</string> <string name="notification_install_failed" msgid="4066039210317521404">"Instalarea nu a reușit"</string> <string name="notification_image_validation_failed" msgid="2720357826403917016">"Nu s-a validat imaginea. Abandonați instalarea."</string> - <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Rulăm un sistem dinamic. Reporniți pentru a folosi versiunea Android inițială."</string> - <string name="notification_action_cancel" msgid="5929299408545961077">"Anulați"</string> + <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Rulăm un sistem dinamic. Repornește pentru a folosi versiunea Android inițială."</string> + <string name="notification_action_cancel" msgid="5929299408545961077">"Anulează"</string> <string name="notification_action_discard" msgid="1817481003134947493">"Renunțați"</string> - <string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"Reporniți"</string> - <string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"Reporniți"</string> + <string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"Repornește"</string> + <string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"Repornește"</string> <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"S-a renunțat la sistemul dinamic"</string> <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"Nu se poate reporni sau încărca sistemul dinamic"</string> <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"Sistemul dinamic nu a fost dezactivat"</string> diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml index 1f156190b5fd..6b793fcd810c 100644 --- a/packages/PackageInstaller/res/values-ro/strings.xml +++ b/packages/PackageInstaller/res/values-ro/strings.xml @@ -29,36 +29,36 @@ <string name="install_failed" msgid="5777824004474125469">"Aplicația nu a fost instalată."</string> <string name="install_failed_blocked" msgid="8512284352994752094">"Instalarea pachetului a fost blocată."</string> <string name="install_failed_conflict" msgid="3493184212162521426">"Aplicația nu a fost instalată deoarece pachetul intră în conflict cu un pachet existent."</string> - <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Aplicația nu a fost instalată deoarece nu este compatibilă cu tableta dvs."</string> - <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Aplicația nu este compatibilă cu televizorul dvs."</string> - <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Aplicația nu a fost instalată deoarece nu este compatibilă cu telefonul dvs."</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Aplicația nu a fost instalată deoarece nu este compatibilă cu tableta."</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Aplicația nu este compatibilă cu televizorul."</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Aplicația nu a fost instalată deoarece nu este compatibilă cu telefonul."</string> <string name="install_failed_invalid_apk" msgid="8581007676422623930">"Aplicația nu a fost instalată deoarece pachetul este nevalid."</string> - <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe tableta dvs."</string> - <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe televizorul dvs."</string> - <string name="install_failed_msg" product="default" msgid="6484461562647915707">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe telefonul dvs."</string> + <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe tabletă."</string> + <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe televizor."</string> + <string name="install_failed_msg" product="default" msgid="6484461562647915707">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe telefon."</string> <string name="launch" msgid="3952550563999890101">"Deschide"</string> <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"Administratorul nu permite instalarea aplicațiilor obținute din surse necunoscute"</string> <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"Aplicațiile necunoscute nu pot fi instalate de acest utilizator"</string> <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"Acest utilizator nu are permisiunea să instaleze aplicații"</string> <string name="ok" msgid="7871959885003339302">"OK"</string> - <string name="manage_applications" msgid="5400164782453975580">"Gestionați aplicații"</string> + <string name="manage_applications" msgid="5400164782453975580">"Gestionează"</string> <string name="out_of_space_dlg_title" msgid="4156690013884649502">"Spațiu de stocare insuficient"</string> - <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată. Eliberați spațiu și încercați din nou."</string> + <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată. Eliberează spațiu și încearcă din nou."</string> <string name="app_not_found_dlg_title" msgid="5107924008597470285">"Aplicația nu a fost găsită"</string> <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Aplicația nu a fost găsită în lista de aplicații instalate."</string> <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Nepermis"</string> <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"Utilizatorul actual nu are permisiune pentru a face această dezinstalare."</string> <string name="generic_error_dlg_title" msgid="5863195085927067752">"Eroare"</string> <string name="generic_error_dlg_text" msgid="5287861443265795232">"Aplicația nu a putut fi dezinstalată."</string> - <string name="uninstall_application_title" msgid="4045420072401428123">"Dezinstalați aplicația"</string> - <string name="uninstall_update_title" msgid="824411791011583031">"Dezinstalați actualizarea"</string> + <string name="uninstall_application_title" msgid="4045420072401428123">"Dezinstalează aplicația"</string> + <string name="uninstall_update_title" msgid="824411791011583031">"Dezinstalează actualizarea"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> face parte din următoarea aplicație:"</string> - <string name="uninstall_application_text" msgid="3816830743706143980">"Doriți să dezinstalați această aplicație?"</string> - <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Doriți să dezinstalați această aplicație pentru "<b>"toți"</b>" utilizatorii? Aplicația și datele acesteia vor fi eliminate de la "<b>"toți"</b>" utilizatorii de pe acest dispozitiv."</string> - <string name="uninstall_application_text_user" msgid="498072714173920526">"Dezinstalați această aplicație pentru utilizatorul <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"Dezinstalezi această aplicație?"</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Dezinstalezi această aplicație pentru "<b>"toți"</b>" utilizatorii? Aplicația și datele acesteia vor fi eliminate de la "<b>"toți"</b>" utilizatorii de pe acest dispozitiv."</string> + <string name="uninstall_application_text_user" msgid="498072714173920526">"Dezinstalezi această aplicație pentru utilizatorul <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Doriți să dezinstalați această aplicație din profilul de serviciu?"</string> - <string name="uninstall_update_text" msgid="863648314632448705">"Înlocuiți această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate."</string> - <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Înlocuiți această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate. Această acțiune va afecta toți utilizatorii dispozitivului, inclusiv pe cei cu profiluri de serviciu."</string> + <string name="uninstall_update_text" msgid="863648314632448705">"Înlocuiești această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate."</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Înlocuiești această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate. Această acțiune va afecta toți utilizatorii dispozitivului, inclusiv pe cei cu profiluri de serviciu."</string> <string name="uninstall_keep_data" msgid="7002379587465487550">"Păstrează <xliff:g id="SIZE">%1$s</xliff:g> din datele aplicației."</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Dezinstalări în curs"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Dezinstalări nereușite"</string> @@ -71,10 +71,10 @@ <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Nu se poate dezinstala aplicația activă de administrare a dispozitivului"</string> <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Nu se poate dezinstala aplicația activă de administrare a dispozitivului pentru <xliff:g id="USERNAME">%1$s</xliff:g>"</string> <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Aplicația este necesară unor utilizatori sau profiluri și a fost dezinstalată pentru alții"</string> - <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Aplicația este necesară pentru profilul dvs. și nu poate fi dezinstalată."</string> + <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Aplicația este necesară pentru profilul tău și nu poate fi dezinstalată."</string> <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Aplicația este necesară administratorului dispozitivului și nu poate fi dezinstalată."</string> - <string name="manage_device_administrators" msgid="3092696419363842816">"Gestionați aplicațiile de administrare dispozitiv"</string> - <string name="manage_users" msgid="1243995386982560813">"Gestionați utilizatorii"</string> + <string name="manage_device_administrators" msgid="3092696419363842816">"Gestionează aplicațiile de administrare dispozitiv"</string> + <string name="manage_users" msgid="1243995386982560813">"Gestionează utilizatorii"</string> <string name="uninstall_failed_msg" msgid="2176744834786696012">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi dezinstalată."</string> <string name="Parse_error_dlg_text" msgid="1661404001063076789">"A apărut o problemă la analizarea pachetului."</string> <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string> @@ -84,10 +84,10 @@ <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Din motive de securitate, tableta dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string> <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Din motive de securitate, televizorul dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string> <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Din motive de securitate, telefonul dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string> - <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați această aplicație, acceptați că sunteți singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> - <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați aplicația, acceptați că sunteți singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> - <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Televizorul și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați această aplicație, acceptați că sunteți singura persoană responsabilă pentru deteriorarea televizorului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> - <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuați"</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi aplicația, accepți că ești singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Televizorul și datele tale cu caracter personal sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea televizorului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string> + <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuă"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Setări"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Se (dez)instalează aplicațiile Wear"</string> <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificare de aplicație instalată"</string> diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml index e0fb0b89ea88..507088fdbe03 100644 --- a/packages/PrintSpooler/res/values-ro/strings.xml +++ b/packages/PrintSpooler/res/values-ro/strings.xml @@ -27,15 +27,15 @@ <string name="label_duplex" msgid="5370037254347072243">"Față-verso"</string> <string name="label_orientation" msgid="2853142581990496477">"Orientare"</string> <string name="label_pages" msgid="7768589729282182230">"Pagini"</string> - <string name="destination_default_text" msgid="5422708056807065710">"Selectați imprimanta"</string> + <string name="destination_default_text" msgid="5422708056807065710">"Selectează imprimanta"</string> <string name="template_all_pages" msgid="3322235982020148762">"Toate cele <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string> <string name="template_page_range" msgid="428638530038286328">"Intervalul de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string> <string name="pages_range_example" msgid="8558694453556945172">"de ex. 1-5, 8, 11-13"</string> - <string name="print_preview" msgid="8010217796057763343">"Previzualizați printarea"</string> - <string name="install_for_print_preview" msgid="6366303997385509332">"Instalați PDF viewer pentru previzualizare"</string> + <string name="print_preview" msgid="8010217796057763343">"Previzualizează printarea"</string> + <string name="install_for_print_preview" msgid="6366303997385509332">"Instalează PDF viewer pentru previzualizare"</string> <string name="printing_app_crashed" msgid="854477616686566398">"Aplicația de printare s-a blocat"</string> <string name="generating_print_job" msgid="3119608742651698916">"Se generează sarcină printare"</string> - <string name="save_as_pdf" msgid="5718454119847596853">"Salvați ca PDF"</string> + <string name="save_as_pdf" msgid="5718454119847596853">"Salvează ca PDF"</string> <string name="all_printers" msgid="5018829726861876202">"Toate imprimantele..."</string> <string name="print_dialog" msgid="32628687461331979">"Caseta de dialog de printare"</string> <string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string> @@ -43,18 +43,18 @@ <string name="summary_template" msgid="8899734908625669193">"Rezumat, copii <xliff:g id="COPIES">%1$s</xliff:g>, dimensiunea paginii <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string> <string name="expand_handle" msgid="7282974448109280522">"Ghidaj de extindere"</string> <string name="collapse_handle" msgid="6886637989442507451">"Ghidaj de restrângere"</string> - <string name="print_button" msgid="645164566271246268">"Printați"</string> - <string name="savetopdf_button" msgid="2976186791686924743">"Salvați în format PDF"</string> + <string name="print_button" msgid="645164566271246268">"Printează"</string> + <string name="savetopdf_button" msgid="2976186791686924743">"Salvează în format PDF"</string> <string name="print_options_expanded" msgid="6944679157471691859">"Opțiuni de printare extinse"</string> <string name="print_options_collapsed" msgid="7455930445670414332">"Opțiuni de printare restrânse"</string> - <string name="search" msgid="5421724265322228497">"Căutați"</string> + <string name="search" msgid="5421724265322228497">"Caută"</string> <string name="all_printers_label" msgid="3178848870161526399">"Toate imprimantele"</string> - <string name="add_print_service_label" msgid="5356702546188981940">"Adăugați un serviciu"</string> + <string name="add_print_service_label" msgid="5356702546188981940">"Adaugă un serviciu"</string> <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Caseta de căutare este afișată"</string> <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Caseta de căutare este ascunsă"</string> - <string name="print_add_printer" msgid="1088656468360653455">"Adăugați o imprimantă"</string> - <string name="print_select_printer" msgid="7388760939873368698">"Selectați imprimanta"</string> - <string name="print_forget_printer" msgid="5035287497291910766">"Omiteți imprimanta"</string> + <string name="print_add_printer" msgid="1088656468360653455">"Adaugă o imprimantă"</string> + <string name="print_select_printer" msgid="7388760939873368698">"Selectează imprimanta"</string> + <string name="print_forget_printer" msgid="5035287497291910766">"Omite imprimanta"</string> <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868"> <item quantity="few"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante găsite</item> <item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante găsite</item> @@ -70,26 +70,26 @@ <string name="print_no_print_services" msgid="8561247706423327966">"Niciun serviciu de printare activat"</string> <string name="print_no_printers" msgid="4869403323900054866">"Nu au fost găsite imprimante"</string> <string name="cannot_add_printer" msgid="7840348733668023106">"Nu pot fi adăugate imprimante"</string> - <string name="select_to_add_printers" msgid="3800709038689830974">"Selectați pentru a adăuga o imprimantă"</string> - <string name="enable_print_service" msgid="3482815747043533842">"Selectați pentru a activa"</string> + <string name="select_to_add_printers" msgid="3800709038689830974">"Selectează pentru a adăuga o imprimantă"</string> + <string name="enable_print_service" msgid="3482815747043533842">"Selectează pentru a activa"</string> <string name="enabled_services_title" msgid="7036986099096582296">"Servicii activate"</string> <string name="recommended_services_title" msgid="3799434882937956924">"Servicii recomandate"</string> <string name="disabled_services_title" msgid="7313253167968363211">"Servicii dezactivate"</string> <string name="all_services_title" msgid="5578662754874906455">"Toate serviciile"</string> <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138"> - <item quantity="few">Instalați pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item> - <item quantity="other">Instalați pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante</item> - <item quantity="one">Instalați pentru a descoperi <xliff:g id="COUNT_0">%1$s</xliff:g> imprimantă</item> + <item quantity="few">Instalează pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item> + <item quantity="other">Instalează pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante</item> + <item quantity="one">Instalează pentru a descoperi <xliff:g id="COUNT_0">%1$s</xliff:g> imprimantă</item> </plurals> <string name="printing_notification_title_template" msgid="295903957762447362">"Se printează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string> <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Se anulează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string> <string name="failed_notification_title_template" msgid="2256217208186530973">"Eroare de printare: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string> <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printare blocată: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string> - <string name="cancel" msgid="4373674107267141885">"Anulați"</string> - <string name="restart" msgid="2472034227037808749">"Reporniți"</string> + <string name="cancel" msgid="4373674107267141885">"Anulează"</string> + <string name="restart" msgid="2472034227037808749">"Repornește"</string> <string name="no_connection_to_printer" msgid="2159246915977282728">"Nu există conexiune la o imprimantă"</string> <string name="reason_unknown" msgid="5507940196503246139">"necunoscut"</string> - <string name="print_service_security_warning_title" msgid="2160752291246775320">"Folosiți <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> + <string name="print_service_security_warning_title" msgid="2160752291246775320">"Folosești <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Documentul poate trece prin unul sau mai multe servere pe calea spre imprimantă."</string> <string-array name="color_mode_labels"> <item msgid="7602948745415174937">"Alb-negru"</item> @@ -105,8 +105,8 @@ <item msgid="3199660090246166812">"Peisaj"</item> </string-array> <string name="print_write_error_message" msgid="5787642615179572543">"Nu s-a putut scrie în fișier."</string> - <string name="print_error_default_message" msgid="8602678405502922346">"Ne pare rău, operațiunea nu a reușit. Încercați din nou."</string> - <string name="print_error_retry" msgid="1426421728784259538">"Reîncercați"</string> + <string name="print_error_default_message" msgid="8602678405502922346">"Ne pare rău, operațiunea nu a reușit. Încearcă din nou."</string> + <string name="print_error_retry" msgid="1426421728784259538">"Reîncearcă"</string> <string name="print_error_printer_unavailable" msgid="8985614415253203381">"Această imprimantă nu este disponibilă momentan."</string> <string name="print_cannot_load_page" msgid="6179560924492912009">"Previzualizarea nu se poate afișa"</string> <string name="print_preparing_preview" msgid="3939930735671364712">"Se pregătește previzualizarea..."</string> diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 1df9059a7c73..c659525d42a5 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -56,8 +56,12 @@ android_library { "SettingsLibDeviceStateRotationLock", "setupdesign", "zxing-core-1.7", + "androidx.room_room-runtime", + ], + plugins: ["androidx.room_room-compiler-plugin"], + resource_dirs: ["res"], srcs: [ diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index 773e5f3929fb..6fe88e1fd37c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -54,7 +54,7 @@ fun createSettingsPage( parameter: List<NamedNavArgument> = emptyList(), arguments: Bundle? = null ): SettingsPage { - return SettingsPage( + return SettingsPage.create( name = SppName.name, displayName = SppName.displayName, parameter = parameter, diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index c8a4650d3f01..c68e918eb449 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -18,14 +18,18 @@ package com.android.settingslib.spa.gallery.home import android.os.Bundle import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.util.getRuntimeArguments +import com.android.settingslib.spa.framework.util.mergeArguments import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.createSettingsPage import com.android.settingslib.spa.gallery.page.ArgumentPageModel import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider @@ -41,27 +45,36 @@ object HomePageProvider : SettingsPageProvider { override val name = SettingsPageProviderEnum.HOME.name override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val owner = createSettingsPage(SettingsPageProviderEnum.HOME) return listOf( - PreferenceMainPageProvider.buildInjectEntry().build(), - ArgumentPageProvider.buildInjectEntry("foo")!!.build(), - SliderPageProvider.buildInjectEntry().build(), - SpinnerPageProvider.buildInjectEntry().build(), - SettingsPagerPageProvider.buildInjectEntry().build(), - FooterPageProvider.buildInjectEntry().build(), - IllustrationPageProvider.buildInjectEntry().build(), - CategoryPageProvider.buildInjectEntry().build(), - ActionButtonPageProvider.buildInjectEntry().build(), + PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(), + SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } @Composable override fun Page(arguments: Bundle?) { + val globalRuntimeArgs = remember { getRuntimeArguments(arguments) } HomeScaffold(title = stringResource(R.string.app_name)) { for (entry in buildEntry(arguments)) { if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) { - entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0)) + entry.UiLayout( + mergeArguments( + listOf( + globalRuntimeArgs, + ArgumentPageModel.buildArgument(intParam = 0) + ) + ) + ) } else { - entry.UiLayout() + entry.UiLayout(globalRuntimeArgs) } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt index cc0a79adaa88..9bf7ad8fb8f7 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt @@ -18,12 +18,15 @@ package com.android.settingslib.spa.gallery.page import android.os.Bundle import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.util.getRuntimeArguments +import com.android.settingslib.spa.framework.util.mergeArguments import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.createSettingsPage import com.android.settingslib.spa.widget.preference.Preference @@ -98,12 +101,20 @@ object ArgumentPageProvider : SettingsPageProvider { @Composable override fun Page(arguments: Bundle?) { + val globalRuntimeArgs = remember { getRuntimeArguments(arguments) } RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) { for (entry in buildEntry(arguments)) { - if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) { - entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments)) + if (entry.toPage != null) { + entry.UiLayout( + mergeArguments( + listOf( + globalRuntimeArgs, + ArgumentPageModel.buildNextArgument(arguments) + ) + ) + ) } else { - entry.UiLayout() + entry.UiLayout(globalRuntimeArgs) } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt index e75d09b7d17c..ee2bde4c2edb 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavType import androidx.navigation.navArgument -import com.android.settingslib.spa.framework.BrowseActivity import com.android.settingslib.spa.framework.common.EntrySearchData import com.android.settingslib.spa.framework.common.PageModel import com.android.settingslib.spa.framework.compose.navigator @@ -92,14 +91,12 @@ class ArgumentPageModel : PageModel() { private var arguments: Bundle? = null private var stringParam: String? = null private var intParam: Int? = null - private var highlightName: String? = null override fun initialize(arguments: Bundle?) { logMsg("init with args " + arguments.toString()) this.arguments = arguments stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments) intParam = parameter.getIntArg(INT_PARAM_NAME, arguments) - highlightName = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME) } @Composable @@ -133,7 +130,8 @@ class ArgumentPageModel : PageModel() { override val title = genPageTitle() override val summary = stateOf(summaryArray.joinToString(", ")) override val onClick = navigator( - SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)) + SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments) + ) } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt index 9fc736c911af..e3416c6f14ca 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt @@ -30,6 +30,7 @@ import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.util.getRuntimeArguments import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.createSettingsPage import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE @@ -44,7 +45,7 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Compan import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.logMsg import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spa.widget.preference.SimplePreferenceMarco +import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.SettingsIcon @@ -73,18 +74,18 @@ object PreferencePageProvider : SettingsPageProvider { entryList.add( createEntry(EntryEnum.SIMPLE_PREFERENCE) .setIsAllowSearch(true) - .setMarco { - logMsg("create marco for ${EntryEnum.SIMPLE_PREFERENCE}") - SimplePreferenceMarco(title = SIMPLE_PREFERENCE_TITLE) + .setMacro { + logMsg("create macro for ${EntryEnum.SIMPLE_PREFERENCE}") + SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE) } .build() ) entryList.add( createEntry(EntryEnum.SUMMARY_PREFERENCE) .setIsAllowSearch(true) - .setMarco { - logMsg("create marco for ${EntryEnum.SUMMARY_PREFERENCE}") - SimplePreferenceMarco( + .setMacro { + logMsg("create macro for ${EntryEnum.SUMMARY_PREFERENCE}") + SimplePreferenceMacro( title = SIMPLE_PREFERENCE_TITLE, summary = SIMPLE_PREFERENCE_SUMMARY, searchKeywords = SIMPLE_PREFERENCE_KEYWORDS, @@ -95,9 +96,9 @@ object PreferencePageProvider : SettingsPageProvider { entryList.add( createEntry(EntryEnum.DISABLED_PREFERENCE) .setIsAllowSearch(true) - .setMarco { - logMsg("create marco for ${EntryEnum.DISABLED_PREFERENCE}") - SimplePreferenceMarco( + .setMacro { + logMsg("create macro for ${EntryEnum.DISABLED_PREFERENCE}") + SimplePreferenceMacro( title = DISABLE_PREFERENCE_TITLE, summary = DISABLE_PREFERENCE_SUMMARY, disabled = true, @@ -166,9 +167,9 @@ object PreferencePageProvider : SettingsPageProvider { fun buildInjectEntry(): SettingsEntryBuilder { return SettingsEntryBuilder.createInject(owner = owner) .setIsAllowSearch(true) - .setMarco { - logMsg("create marco for INJECT entry") - SimplePreferenceMarco( + .setMacro { + logMsg("create macro for INJECT entry") + SimplePreferenceMacro( title = PAGE_TITLE, clickRoute = SettingsPageProviderEnum.PREFERENCE.name ) @@ -177,9 +178,10 @@ object PreferencePageProvider : SettingsPageProvider { @Composable override fun Page(arguments: Bundle?) { + val globalRuntimeArgs = remember { getRuntimeArguments(arguments) } RegularScaffold(title = PAGE_TITLE) { for (entry in buildEntry(arguments)) { - entry.UiLayout() + entry.UiLayout(globalRuntimeArgs) } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt index 9a525bf5426d..a4713b993af0 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt @@ -35,7 +35,7 @@ import com.android.settingslib.spa.widget.ui.CategoryTitle private const val TITLE = "Sample Category" object CategoryPageProvider : SettingsPageProvider { - override val name = "Spinner" + override val name = "Category" fun buildInjectEntry(): SettingsEntryBuilder { return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt index c4e88e158e7a..c698d9c7ca9a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt @@ -153,7 +153,7 @@ open class DebugActivity( "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})" override val summary = pageWithEntry.page.formatArguments().toState() override val onClick = - navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id()}") + navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}") }) } } @@ -172,7 +172,7 @@ open class DebugActivity( val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "") val pageWithEntry = entryRepository.getPageWithEntry(id)!! RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") { - Text(text = "id = ${pageWithEntry.page.id()}") + Text(text = "id = ${pageWithEntry.page.id}") Text(text = pageWithEntry.page.formatArguments()) Text(text = "Entry size: ${pageWithEntry.entries.size}") Preference(model = object : PreferenceModel { @@ -206,7 +206,7 @@ open class DebugActivity( override val title = entry.displayTitle() override val summary = "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState() - override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id()}") + override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") }) } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt index 33f1eb5b723d..d923c1ce83e8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt @@ -38,8 +38,9 @@ import com.android.settingslib.spa.framework.common.SettingsPage * For gallery, AuthorityPath = com.android.spa.gallery.provider * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider * Some examples: - * $ adb shell content query --uri content://<AuthorityPath>/page_start + * $ adb shell content query --uri content://<AuthorityPath>/page_debug * $ adb shell content query --uri content://<AuthorityPath>/page_info + * $ adb shell content query --uri content://<AuthorityPath>/entry_info */ open class EntryProvider( private val entryRepository: SettingsEntryRepository, @@ -50,6 +51,7 @@ open class EntryProvider( * Enum to define all column names in provider. */ enum class ColumnEnum(val id: String) { + // Columns related to page PAGE_ID("pageId"), PAGE_NAME("pageName"), PAGE_ROUTE("pageRoute"), @@ -57,6 +59,13 @@ open class EntryProvider( PAGE_ENTRY_COUNT("entryCount"), HAS_RUNTIME_PARAM("hasRuntimeParam"), PAGE_START_ADB("pageStartAdb"), + + // Columns related to entry + ENTRY_ID("entryId"), + ENTRY_NAME("entryName"), + ENTRY_ROUTE("entryRoute"), + ENTRY_TITLE("entryTitle"), + ENTRY_SEARCH_KEYWORD("entrySearchKw"), } /** @@ -67,12 +76,15 @@ open class EntryProvider( val queryMatchCode: Int, val columnNames: List<ColumnEnum> ) { - PAGE_START_COMMAND( - "page_start", 1, + // For debug + PAGE_DEBUG_QUERY( + "page_debug", 1, listOf(ColumnEnum.PAGE_START_ADB) ), + + // page related queries. PAGE_INFO_QUERY( - "page_info", 2, + "page_info", 100, listOf( ColumnEnum.PAGE_ID, ColumnEnum.PAGE_NAME, @@ -82,9 +94,24 @@ open class EntryProvider( ColumnEnum.HAS_RUNTIME_PARAM, ) ), + + // entry related queries + ENTRY_INFO_QUERY( + "entry_info", 200, + listOf( + ColumnEnum.ENTRY_ID, + ColumnEnum.ENTRY_NAME, + ColumnEnum.ENTRY_ROUTE, + ColumnEnum.ENTRY_TITLE, + ColumnEnum.ENTRY_SEARCH_KEYWORD, + ) + ) } - private var uriMatcher: UriMatcher? = null + private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) + private fun addUri(authority: String, query: QueryEnum) { + uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode) + } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { TODO("Implement this to handle requests to delete one or more rows") @@ -115,18 +142,10 @@ open class EntryProvider( } override fun attachInfo(context: Context?, info: ProviderInfo?) { - uriMatcher = UriMatcher(UriMatcher.NO_MATCH) if (info != null) { - uriMatcher!!.addURI( - info.authority, - QueryEnum.PAGE_START_COMMAND.queryPath, - QueryEnum.PAGE_START_COMMAND.queryMatchCode - ) - uriMatcher!!.addURI( - info.authority, - QueryEnum.PAGE_INFO_QUERY.queryPath, - QueryEnum.PAGE_INFO_QUERY.queryMatchCode - ) + addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY) + addUri(info.authority, QueryEnum.PAGE_INFO_QUERY) + addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY) } super.attachInfo(context, info) } @@ -139,9 +158,10 @@ open class EntryProvider( sortOrder: String? ): Cursor? { return try { - when (uriMatcher!!.match(uri)) { - QueryEnum.PAGE_START_COMMAND.queryMatchCode -> queryPageStartCommand() + when (uriMatcher.match(uri)) { + QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug() QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() + QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo() else -> throw UnsupportedOperationException("Unknown Uri $uri") } } catch (e: UnsupportedOperationException) { @@ -152,8 +172,8 @@ open class EntryProvider( } } - private fun queryPageStartCommand(): Cursor { - val cursor = MatrixCursor(QueryEnum.PAGE_START_COMMAND.getColumns()) + private fun queryPageDebug(): Cursor { + val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) for (pageWithEntry in entryRepository.getAllPageWithEntry()) { val command = createBrowsePageAdbCommand(pageWithEntry.page) if (command != null) { @@ -168,7 +188,7 @@ open class EntryProvider( for (pageWithEntry in entryRepository.getAllPageWithEntry()) { val page = pageWithEntry.page cursor.newRow() - .add(ColumnEnum.PAGE_ID.id, page.id()) + .add(ColumnEnum.PAGE_ID.id, page.id) .add(ColumnEnum.PAGE_NAME.id, page.displayName) .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute()) .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size) @@ -181,6 +201,24 @@ open class EntryProvider( return cursor } + private fun queryEntryInfo(): Cursor { + val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) + for (entry in entryRepository.getAllEntries()) { + // We can add runtime arguments if necessary + val searchData = entry.getSearchData() + cursor.newRow() + .add(ColumnEnum.ENTRY_ID.id, entry.id) + .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) + .add(ColumnEnum.ENTRY_ROUTE.id, entry.buildRoute()) + .add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "") + .add( + ColumnEnum.ENTRY_SEARCH_KEYWORD.id, + searchData?.keyword ?: emptyList<String>() + ) + } + return cursor + } + private fun createBrowsePageIntent(page: SettingsPage): Intent { if (context == null || browseActivityClass == null || page.hasRuntimeParam()) return Intent() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt index 8399d125b24e..9ec0c01b4b38 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt @@ -19,11 +19,10 @@ package com.android.settingslib.spa.framework.common import androidx.compose.runtime.Composable /** - * Defines interface of a entry marco, which contains all entry functions to support different + * Defines interface of a entry macro, which contains entry functions to support different * scenarios, such as browsing (UiLayout), search, etc. - * SPA team will rebuild some entry marcos, in order to make the entry creation easier. */ -interface EntryMarco { +interface EntryMacro { @Composable fun UiLayout() {} fun getSearchData(): EntrySearchData? = null diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index 2239066201bf..81d0bffca643 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -17,8 +17,13 @@ package com.android.settingslib.spa.framework.common import android.os.Bundle +import android.widget.Toast import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.platform.LocalContext +import com.android.settingslib.spa.framework.BrowseActivity.Companion.HIGHLIGHT_ENTRY_PARAM_NAME const val INJECT_ENTRY_NAME = "INJECT" const val ROOT_ENTRY_NAME = "ROOT" @@ -27,6 +32,9 @@ const val ROOT_ENTRY_NAME = "ROOT" * Defines data of a Settings entry. */ data class SettingsEntry( + // The unique id of this entry, which is computed by name + owner + fromPage + toPage. + val id: String, + // The name of the page, which is used to compute the unique id, and need to be stable. private val name: String, @@ -67,14 +75,9 @@ data class SettingsEntry( */ private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {}, ) { - // The unique id of this entry, which is computed by name + owner + fromPage + toPage. - fun id(): String { - return "$name:${owner.id()}(${fromPage?.id()}-${toPage?.id()})".toHashId() - } - fun formatContent(): String { val content = listOf( - "id = ${id()}", + "id = $id", "owner = ${owner.formatDisplayTitle()}", "linkFrom = ${fromPage?.formatDisplayTitle()}", "linkTo = ${toPage?.formatDisplayTitle()}", @@ -94,7 +97,7 @@ data class SettingsEntry( } fun buildRoute(): String { - return containerPage().buildRoute(id()) + return containerPage().buildRoute(id) } fun hasRuntimeParam(): Boolean { @@ -104,6 +107,7 @@ data class SettingsEntry( private fun fullArgument(runtimeArguments: Bundle? = null): Bundle { val arguments = Bundle() if (owner.arguments != null) arguments.putAll(owner.arguments) + // Put runtime args later, which can override page args. if (runtimeArguments != null) arguments.putAll(runtimeArguments) return arguments } @@ -114,6 +118,15 @@ data class SettingsEntry( @Composable fun UiLayout(runtimeArguments: Bundle? = null) { + val context = LocalContext.current + val highlight = rememberSaveable { + mutableStateOf(runtimeArguments?.getString(HIGHLIGHT_ENTRY_PARAM_NAME) == id) + } + if (highlight.value) { + highlight.value = false + // TODO: Add highlight entry logic + Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show() + } uiLayoutImpl(fullArgument(runtimeArguments)) } } @@ -135,6 +148,7 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings fun build(): SettingsEntry { return SettingsEntry( + id = id(), name = name, owner = owner, displayName = displayName, @@ -171,11 +185,11 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings return this } - fun setMarco(fn: (arguments: Bundle?) -> EntryMarco): SettingsEntryBuilder { + fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder { setSearchDataFn { fn(it).getSearchData() } setUiLayoutFn { - val marco = remember { fn(it) } - marco.UiLayout() + val macro = remember { fn(it) } + macro.UiLayout() } return this } @@ -190,6 +204,11 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings return this } + // The unique id of this entry, which is computed by name + owner + fromPage + toPage. + private fun id(): String { + return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId() + } + companion object { fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder { return SettingsEntryBuilder(entryName, owner) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt index 77f064d35eb7..b6f6203209b1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt @@ -44,29 +44,23 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) { val entryQueue = LinkedList<SettingsEntry>() for (page in sppRepository.getAllRootPages()) { val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build() - val rootEntryId = rootEntry.id() - if (!entryMap.containsKey(rootEntryId)) { + if (!entryMap.containsKey(rootEntry.id)) { entryQueue.push(rootEntry) - entryMap.put(rootEntryId, rootEntry) + entryMap.put(rootEntry.id, rootEntry) } } while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) { val entry = entryQueue.pop() val page = entry.toPage - val pageId = page?.id() - if (pageId == null || pageWithEntryMap.containsKey(pageId)) continue + if (page == null || pageWithEntryMap.containsKey(page.id)) continue val spp = sppRepository.getProviderOrNull(page.name) ?: continue - val newEntries = spp.buildEntry(page.arguments).map { - // Set from-page if it is missing. - if (it.fromPage == null) it.copy(fromPage = page) else it - } - pageWithEntryMap[pageId] = SettingsPageWithEntry(page, newEntries) + val newEntries = spp.buildEntry(page.arguments) + pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries) for (newEntry in newEntries) { - val newEntryId = newEntry.id() - if (!entryMap.containsKey(newEntryId)) { + if (!entryMap.containsKey(newEntry.id)) { entryQueue.push(newEntry) - entryMap.put(newEntryId, newEntry) + entryMap.put(newEntry.id, newEntry) } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 124743a23274..0c301b937cce 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -26,6 +26,9 @@ import com.android.settingslib.spa.framework.util.navLink * Defines data to identify a Settings page. */ data class SettingsPage( + // The unique id of this page, which is computed by name + normalized(arguments) + val id: String, + // The name of the page, which is used to compute the unique id, and need to be stable. val name: String, @@ -41,17 +44,28 @@ data class SettingsPage( companion object { fun create( name: String, + displayName: String? = null, parameter: List<NamedNavArgument> = emptyList(), arguments: Bundle? = null ): SettingsPage { - return SettingsPage(name, name, parameter, arguments) + return SettingsPage( + id = id(name, parameter, arguments), + name = name, + displayName = displayName ?: name, + parameter = parameter, + arguments = arguments + ) } - } - // The unique id of this page, which is computed by name + normalized(arguments) - fun id(): String { - val normArguments = parameter.normalize(arguments) - return "$name:${normArguments?.toString()}".toHashId() + // The unique id of this page, which is computed by name + normalized(arguments) + private fun id( + name: String, + parameter: List<NamedNavArgument> = emptyList(), + arguments: Bundle? = null + ): String { + val normArguments = parameter.normalize(arguments) + return "$name:${normArguments?.toString()}".toHashId() + } } // Returns if this Settings Page is created by the given Spp. @@ -69,12 +83,12 @@ data class SettingsPage( return "$displayName ${formatArguments()}" } - fun buildRoute(highlightEntryName: String? = null): String { + fun buildRoute(highlightEntryId: String? = null): String { val highlightParam = - if (highlightEntryName == null) + if (highlightEntryId == null) "" else - "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName" + "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryId" return name + parameter.navLink(arguments) + highlightParam } @@ -114,5 +128,5 @@ private fun List<NamedNavArgument>.hasRuntimeParam(arguments: Bundle? = null): B } fun String.toHashId(): String { - return this.hashCode().toString(36) + return this.hashCode().toUInt().toString(36) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt new file mode 100644 index 000000000000..1589b04813cf --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DisposableBroadcastReceiverAsUser.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.compose + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver + +/** + * A [BroadcastReceiver] which registered when on start and unregistered when on stop. + */ +@Composable +fun DisposableBroadcastReceiverAsUser( + userId: Int, + intentFilter: IntentFilter, + onReceive: (Intent) -> Unit, +) { + val broadcastReceiver = remember { + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + onReceive(intent) + } + } + } + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_START) { + context.registerReceiverAsUser( + broadcastReceiver, UserHandle.of(userId), intentFilter, null, null) + } else if (event == Lifecycle.Event.ON_STOP) { + context.unregisterReceiver(broadcastReceiver) + } + } + + lifecycleOwner.lifecycle.addObserver(observer) + + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt index 999d8d7dd16a..d801840565ed 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt @@ -19,25 +19,33 @@ package com.android.settingslib.spa.framework.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State -import androidx.compose.runtime.snapshotFlow -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +/** + * Returns a [Flow] whose values are a list which containing the results of asynchronously applying + * the given [transform] function to each element in the original flow's list. + */ inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> = map { list -> list.asyncMap(transform) } -@OptIn(ExperimentalCoroutinesApi::class) -inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> = - flatMapLatest { snapshotFlow { block(it).value } } +/** + * Delays the flow a little bit, wait the other flow's first value. + */ +fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> = + combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value } -fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> = - combine(flow.distinctUntilChangedBy {}) { value, _ -> value } +/** + * Returns a [Flow] whose values are generated list by combining the most recently emitted non null + * values by each flow. + */ +inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine( + flows.asList(), +) { array: Array<T?> -> array.filterNotNull() } class StateFlowBridge<T> { private val stateFlow = MutableStateFlow<T?>(null) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt index d7d7750e37b9..452f76abb984 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework.util import android.os.Bundle import androidx.navigation.NamedNavArgument import androidx.navigation.NavType +import com.android.settingslib.spa.framework.BrowseActivity fun List<NamedNavArgument>.navRoute(): String { return this.joinToString("") { argument -> "/{${argument.name}}" } @@ -70,3 +71,21 @@ fun List<NamedNavArgument>.containsIntArg(name: String): Boolean { } return false } + +fun getRuntimeArguments(arguments: Bundle? = null): Bundle { + val res = Bundle() + val highlightEntry = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME) + if (highlightEntry != null) { + res.putString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME, highlightEntry) + } + // Append more general runtime arguments here + return res +} + +fun mergeArguments(argsList: List<Bundle?>): Bundle { + val res = Bundle() + for (args in argsList) { + if (args != null) res.putAll(args) + } + return res +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 3555ec746789..a9d1c3735ab7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.widget.button import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -39,6 +40,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -54,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.divider data class ActionButton( val text: String, val imageVector: ImageVector, + val enabled: Boolean = true, val onClick: () -> Unit, ) @@ -79,10 +82,15 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { modifier = Modifier .weight(1f) .fillMaxHeight(), + enabled = actionButton.enabled, + // Because buttons could appear, disappear or change positions, reset the interaction source + // to prevent highlight the wrong button. + interactionSource = remember(actionButton) { MutableInteractionSource() }, shape = RectangleShape, colors = ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.surface, + containerColor = SettingsTheme.colorScheme.surface, contentColor = SettingsTheme.colorScheme.categoryTitle, + disabledContainerColor = SettingsTheme.colorScheme.surface, ), contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp), ) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt index 39b8d57d9da4..47abc87327ad 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt @@ -22,34 +22,34 @@ import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import com.android.settingslib.spa.framework.common.EntryMarco +import com.android.settingslib.spa.framework.common.EntryMacro import com.android.settingslib.spa.framework.common.EntrySearchData import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.ui.createSettingsIcon -data class SimplePreferenceMarco( +data class SimplePreferenceMacro( val title: String, val summary: String? = null, val icon: ImageVector? = null, val disabled: Boolean = false, val clickRoute: String? = null, val searchKeywords: List<String> = emptyList(), -) : EntryMarco { +) : EntryMacro { @Composable override fun UiLayout() { Preference(model = object : PreferenceModel { - override val title: String = this@SimplePreferenceMarco.title - override val summary = stateOf(this@SimplePreferenceMarco.summary ?: "") - override val icon = createSettingsIcon(this@SimplePreferenceMarco.icon) - override val enabled = stateOf(!this@SimplePreferenceMarco.disabled) + override val title: String = this@SimplePreferenceMacro.title + override val summary = stateOf(this@SimplePreferenceMacro.summary ?: "") + override val icon = createSettingsIcon(this@SimplePreferenceMacro.icon) + override val enabled = stateOf(!this@SimplePreferenceMacro.disabled) override val onClick = navigator(clickRoute) }) } override fun getSearchData(): EntrySearchData { return EntrySearchData( - title = this@SimplePreferenceMarco.title, + title = this@SimplePreferenceMacro.title, keyword = searchKeywords ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt index 99a08abad8b7..96e3e77e9322 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt @@ -17,12 +17,23 @@ package com.android.settingslib.spaprivileged.model.app import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import android.os.UserHandle +/** The user id for a given application. */ val ApplicationInfo.userId: Int get() = UserHandle.getUserId(uid) +/** The [UserHandle] for a given application. */ val ApplicationInfo.userHandle: UserHandle get() = UserHandle.getUserHandleForUid(uid) +/** Checks whether a flag is associated with the application. */ +fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0 + +/** Checks whether the application is disabled until used. */ +fun ApplicationInfo.isDisabledUntilUsed(): Boolean = + enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED + +/** Converts to the route string which used in navigation. */ fun ApplicationInfo.toRoute() = "$packageName/$userId" diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt index ba8af5400e18..5c5caf598717 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt @@ -55,7 +55,7 @@ object PackageManagers { iPackageManager.isPackageAvailable(it, userId) }.toSet() - private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = + fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? = try { PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId) } catch (e: PackageManager.NameNotFoundException) { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 80b2364ce68d..883eddff8054 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -63,7 +63,7 @@ internal class TogglePermissionAppInfoPageProvider( override val parameter = PAGE_PARAMETER override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { - val owner = SettingsPage.create(name, parameter, arguments) + val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments) val entryList = mutableListOf<SettingsEntry>() entryList.add( SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build() @@ -108,7 +108,10 @@ internal class TogglePermissionAppInfoPageProvider( fun buildPageData(permissionType: String): SettingsPage { return SettingsPage.create( - PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType)) + name = PAGE_NAME, + parameter = PAGE_PARAMETER, + arguments = bundleOf(PERMISSION to permissionType) + ) } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index bd60bd3699c1..ec7d75e969df 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -63,7 +63,7 @@ internal class TogglePermissionAppListPageProvider( override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { val permissionType = parameter.getStringArg(PERMISSION, arguments)!! - val appListPage = SettingsPage.create(name, parameter, arguments) + val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments) val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType) val entryList = mutableListOf<SettingsEntry>() // TODO: add more categories, such as personal, work, cloned, etc. @@ -117,7 +117,10 @@ internal class TogglePermissionAppListPageProvider( listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>, ): SettingsEntryBuilder { val appListPage = SettingsPage.create( - PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType)) + name = PAGE_NAME, + parameter = PAGE_PARAMETER, + arguments = bundleOf(PERMISSION to permissionType) + ) return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false) .setUiLayoutFn { val listModel = rememberContext(listModelSupplier) diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 363cd0e96db7..314683bbe8ed 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -631,7 +631,7 @@ <string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string> <string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string> <string name="data_connection_carrier_wifi" msgid="8932949159370130465">"W+"</string> - <string name="cell_data_off_content_description" msgid="2280700839891636498">"Мобильдік деректер өшірулі"</string> + <string name="cell_data_off_content_description" msgid="2280700839891636498">"Мобильдік интернет өшірулі"</string> <string name="not_default_data_content_description" msgid="6517068332106592887">"Деректерді пайдалануға реттелмеген."</string> <string name="accessibility_no_phone" msgid="2687419663127582503">"Телефон жоқ."</string> <string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефон бір баған."</string> diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml index 987b9c36caac..faa15c253fb4 100644 --- a/packages/SettingsLib/res/values-ro/arrays.xml +++ b/packages/SettingsLib/res/values-ro/arrays.xml @@ -113,7 +113,7 @@ <item msgid="8887519571067543785">"96,0 kHz"</item> </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_summaries"> - <item msgid="2284090879080331090">"Folosiți selectarea sistemului (prestabilit)"</item> + <item msgid="2284090879080331090">"Folosește selectarea sistemului (prestabilit)"</item> <item msgid="1872276250541651186">"44,1 kHz"</item> <item msgid="8736780630001704004">"48,0 kHz"</item> <item msgid="7698585706868856888">"88,2 kHz"</item> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 8fb09ac4672e..04c58aecb2b0 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -133,8 +133,8 @@ <string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Folosește pentru profilul pentru conținut media audio"</string> <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Folosește pentru componenta audio a telefonului"</string> <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Folosește pentru transferul de fișiere"</string> - <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilizați pentru introducere date"</string> - <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Folosiți pentru aparatele auditive"</string> + <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Folosește pentru introducere date"</string> + <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Folosește pentru aparatele auditive"</string> <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Folosește pentru LE_AUDIO"</string> <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Asociază"</string> <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"CONECTEAZĂ"</string> @@ -196,7 +196,7 @@ <string name="tts_status_not_supported" msgid="2702997696245523743">"<xliff:g id="LOCALE">%1$s</xliff:g> nu este acceptată"</string> <string name="tts_status_checking" msgid="8026559918948285013">"Se verifică…"</string> <string name="tts_engine_settings_title" msgid="7849477533103566291">"Setări pentru <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string> - <string name="tts_engine_settings_button" msgid="477155276199968948">"Lansați setările motorului"</string> + <string name="tts_engine_settings_button" msgid="477155276199968948">"Lansează setările motorului"</string> <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Motor preferat"</string> <string name="tts_general_section_title" msgid="8919671529502364567">"Preferințe generale"</string> <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Resetează tonalitatea vorbirii"</string> @@ -279,11 +279,11 @@ <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versiunea AVRCP pentru Bluetooth"</string> <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selectează versiunea AVRCP pentru Bluetooth"</string> <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versiunea MAP pentru Bluetooth"</string> - <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selectați versiunea MAP pentru Bluetooth"</string> + <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selectează versiunea MAP pentru Bluetooth"</string> <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string> <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Declanșează codecul audio Bluetooth\nSelecție"</string> <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Rată de eșantionare audio Bluetooth"</string> - <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Declanșați codecul audio Bluetooth\nSelecție: rată de eșantionare"</string> + <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Declanșează codecul audio Bluetooth\nSelecție: rată de eșantionare"</string> <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"O opțiune inactivă înseamnă incompatibilitate cu telefonul sau setul căști-microfon"</string> <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Biți audio Bluetooth per eșantion"</string> <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Declanșează codecul audio Bluetooth\nSelecție: biți per eșantion"</string> @@ -299,7 +299,7 @@ <string name="private_dns_mode_provider" msgid="3619040641762557028">"Nume de gazdă al furnizorului de DNS privat"</string> <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"Introdu numele de gazdă al furnizorului de DNS"</string> <string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Nu s-a putut conecta"</string> - <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Afișați opțiunile pentru certificarea Ecran wireless"</string> + <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Afișează opțiunile pentru certificarea Ecran wireless"</string> <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Mărește nivelul de înregistrare prin Wi‑Fi, afișează după SSID RSSI în Selectorul Wi‑Fi"</string> <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduce descărcarea bateriei și îmbunătățește performanța rețelei"</string> <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"Când acest mod este activat, adresa MAC a dispozitivului se poate schimba de fiecare dată când se conectează la o rețea care are activată randomizarea MAC."</string> @@ -316,16 +316,16 @@ <string name="allow_mock_location" msgid="2102650981552527884">"Permite locațiile fictive"</string> <string name="allow_mock_location_summary" msgid="179780881081354579">"Permite locațiile fictive"</string> <string name="debug_view_attributes" msgid="3539609843984208216">"Activează inspectarea atributelor de vizualizare"</string> - <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Păstrați întotdeauna conexiunea de date mobile activată, chiar și atunci când funcția Wi‑Fi este activată (pentru comutarea rapidă între rețele)."</string> + <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Păstrează întotdeauna conexiunea de date mobile activată, chiar și atunci când funcția Wi‑Fi este activată (pentru comutarea rapidă între rețele)."</string> <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Folosește accelerarea hardware pentru tethering, dacă este disponibilă"</string> - <string name="adb_warning_title" msgid="7708653449506485728">"Permiteți remedierea erorilor prin USB?"</string> - <string name="adb_warning_message" msgid="8145270656419669221">"Remedierea erorilor prin USB are exclusiv scopuri de dezvoltare. Utilizați-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string> + <string name="adb_warning_title" msgid="7708653449506485728">"Permiți remedierea erorilor prin USB?"</string> + <string name="adb_warning_message" msgid="8145270656419669221">"Remedierea erorilor prin USB are exclusiv scopuri de dezvoltare. Folosește-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Permiți remedierea erorilor wireless?"</string> <string name="adbwifi_warning_message" msgid="8005936574322702388">"Remedierea erorilor wireless are exclusiv scopuri de dezvoltare. Folosește-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string> <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoci accesul la remedierea erorilor prin USB de pe toate computerele pe care le-ai autorizat anterior?"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"Permiți setările pentru dezvoltare?"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"Aceste setări sunt destinate exclusiv utilizării pentru dezvoltare. Din cauza lor, este posibil ca dispozitivul tău și aplicațiile de pe acesta să nu mai funcționeze sau să funcționeze necorespunzător."</string> - <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verificați aplicațiile prin USB"</string> + <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verifică aplicațiile prin USB"</string> <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verifică aplicațiile instalate utilizând ADB/ADT, pentru a detecta un comportament dăunător."</string> <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Vor fi afișate dispozitivele Bluetooth fără nume (numai adresele MAC)"</string> <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Dezactivează funcția Bluetooth de volum absolut în cazul problemelor de volum apărute la dispozitivele la distanță, cum ar fi volumul mult prea ridicat sau lipsa de control asupra acestuia."</string> @@ -383,7 +383,7 @@ <string name="window_animation_scale_title" msgid="5236381298376812508">"Scară animație fereastră"</string> <string name="transition_animation_scale_title" msgid="1278477690695439337">"Scară tranziție animații"</string> <string name="animator_duration_scale_title" msgid="7082913931326085176">"Scară durată Animator"</string> - <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulați afișaje secundare"</string> + <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulează afișaje secundare"</string> <string name="debug_applications_category" msgid="5394089406638954196">"Aplicații"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"Nu păstra activitățile"</string> <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Elimină activitățile imediat ce utilizatorul le închide"</string> @@ -394,10 +394,10 @@ <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Afișează avertisment pe ecran când o aplicație postează o notificare fără canal valid"</string> <string name="force_allow_on_external" msgid="9187902444231637880">"Forțează accesul aplicațiilor la stocarea externă"</string> <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Permite scrierea oricărei aplicații eligibile în stocarea externă, indiferent de valorile manifestului"</string> - <string name="force_resizable_activities" msgid="7143612144399959606">"Forțați redimensionarea activităților"</string> - <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permiteți redimensionarea tuturor activităților pentru modul cu ferestre multiple, indiferent de valorile manifestului."</string> + <string name="force_resizable_activities" msgid="7143612144399959606">"Forțează redimensionarea activităților"</string> + <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permite redimensionarea tuturor activităților pentru modul cu ferestre multiple, indiferent de valorile manifestului."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activează ferestrele cu formă liberă"</string> - <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activați compatibilitatea pentru ferestrele experimentale cu formă liberă."</string> + <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activează compatibilitatea pentru ferestrele experimentale cu formă liberă."</string> <string name="desktop_mode" msgid="2389067840550544462">"Modul desktop"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Parolă backup computer"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"În prezent, backupurile complete pe computer nu sunt protejate"</string> @@ -439,7 +439,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corecția culorii poate fi utilă dacă doriți:<br/> <ol> <li>&nbsp;să vedeți mai precis culorile;</li> <li>&nbsp;să eliminați culorile pentru a vă concentra mai bine.</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corecția culorii poate fi utilă dacă vrei:<br/> <ol> <li>&nbsp;să vezi mai precis culorile;</li> <li>&nbsp;să elimini culorile pentru a te concentra mai bine.</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -563,10 +563,10 @@ <string name="user_add_user_title" msgid="5457079143694924885">"Adaugi un utilizator nou?"</string> <string name="user_add_user_message_long" msgid="1527434966294733380">"Poți să permiți accesul la acest dispozitiv altor persoane creând utilizatori suplimentari. Fiecare utilizator are propriul spațiu, pe care îl poate personaliza cu aplicații, imagini de fundal etc. De asemenea, utilizatorii pot ajusta setările dispozitivului, cum ar fi setările pentru Wi-Fi, care îi afectează pe toți ceilalți utilizatori.\n\nDupă ce adaugi un utilizator nou, acesta trebuie să-și configureze spațiul.\n\nOricare dintre utilizatori poate actualiza aplicațiile pentru toți ceilalți utilizatori. Este posibil ca setările de accesibilitate și serviciile să nu se transfere la noul utilizator."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"Când adaugi un utilizator nou, acesta trebuie să-și configureze spațiul.\n\nOrice utilizator poate actualiza aplicațiile pentru toți ceilalți utilizatori."</string> - <string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurați utilizatorul acum?"</string> + <string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurezi utilizatorul acum?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Asigură-te că utilizatorul are posibilitatea de a prelua dispozitivul și de a-și configura spațiul"</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Configurezi profilul acum?"</string> - <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Configurați acum"</string> + <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Configurează acum"</string> <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Nu acum"</string> <string name="user_add_user_type_title" msgid="551279664052914497">"Adaugă"</string> <string name="user_new_user_name" msgid="60979820612818840">"Utilizator nou"</string> @@ -583,7 +583,7 @@ <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string> <string name="user_add_user" msgid="7876449291500212468">"Adaugă un utilizator"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string> - <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Șterge invitatul"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"Resetezi sesiunea pentru invitați"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Resetezi invitatul?"</string> <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Excludeți invitatul?"</string> @@ -594,7 +594,7 @@ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Astfel, va începe o nouă sesiune pentru invitați și se vor șterge toate aplicațiile și datele din sesiunea actuală"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Ieși din modul pentru invitați?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Se vor șterge toate aplicațiile și datele din sesiunea pentru invitați actuală"</string> - <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ieșiți"</string> + <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ieși"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Salvezi activitatea invitatului?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Salvează activitatea din sesiunea actuală sau șterge aplicațiile și datele"</string> <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Șterge"</string> @@ -603,11 +603,11 @@ <string name="guest_reset_button" msgid="2515069346223503479">"Resetează sesiunea pentru invitați"</string> <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Ieși din modul pentru invitați"</string> <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toate activitățile vor fi șterse la ieșire"</string> - <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puteți să salvați sau să ștergeți activitatea la ieșire"</string> + <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Poți să salvezi sau să ștergi activitatea la ieșire"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetează pentru a șterge acum activitatea din sesiune sau salvează ori șterge activitatea la ieșire"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fă o fotografie"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Alege o imagine"</string> - <string name="user_image_photo_selector" msgid="433658323306627093">"Selectați fotografia"</string> + <string name="user_image_photo_selector" msgid="433658323306627093">"Selectează fotografia"</string> <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Prea multe încercări incorecte. Datele de pe acest dispozitiv vor fi șterse."</string> <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Prea multe încercări incorecte. Acest utilizator va fi șters."</string> <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Prea multe încercări incorecte. Acest profil de serviciu și datele sale vor fi șterse."</string> @@ -652,7 +652,7 @@ <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"Alege aspectul tastaturii"</string> <string name="keyboard_layout_default_label" msgid="1997292217218546957">"Prestabilit"</string> <string name="turn_screen_on_title" msgid="3266937298097573424">"Activează ecranul"</string> - <string name="allow_turn_screen_on" msgid="6194845766392742639">"Permiteți activarea ecranului"</string> + <string name="allow_turn_screen_on" msgid="6194845766392742639">"Permite activarea ecranului"</string> <string name="allow_turn_screen_on_description" msgid="43834403291575164">"Permite unei aplicații să activeze ecranul. Dacă acorzi permisiunea, aplicația poate să activeze oricând ecranul, fără intenția ta explicită."</string> <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"Oprești difuzarea <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Dacă difuzezi <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbi rezultatul, difuzarea actuală se va opri"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index fea7475fc087..5c796af84fef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -237,14 +237,10 @@ public class BluetoothUtils { * @return true if it supports advanced metadata, false otherwise. */ public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) { - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, - true)) { - Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); + if (!isAdvancedHeaderEnabled()) { return false; } - // The metadata is for Android R - if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { - Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true"); + if (isUntetheredHeadset(bluetoothDevice)) { return true; } // The metadata is for Android S @@ -260,6 +256,47 @@ public class BluetoothUtils { } /** + * Check if the Bluetooth device is supports advanced metadata and an untethered headset + * + * @param bluetoothDevice the BluetoothDevice to get metadata + * @return true if it supports advanced metadata and an untethered headset, false otherwise. + */ + public static boolean isAdvancedUntetheredDevice(@NonNull BluetoothDevice bluetoothDevice) { + if (!isAdvancedHeaderEnabled()) { + return false; + } + if (isUntetheredHeadset(bluetoothDevice)) { + return true; + } + // The metadata is for Android S + String deviceType = getStringMetaData(bluetoothDevice, + BluetoothDevice.METADATA_DEVICE_TYPE); + if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { + Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device "); + return true; + } + return false; + } + + private static boolean isAdvancedHeaderEnabled() { + if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, + true)) { + Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); + return false; + } + return true; + } + + private static boolean isUntetheredHeadset(@NonNull BluetoothDevice bluetoothDevice) { + // The metadata is for Android R + if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true"); + return true; + } + return false; + } + + /** * Create an Icon pointing to a drawable. */ public static IconCompat createIconWithDrawable(Drawable drawable) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 123c01b6e12f..79fb56602328 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -210,13 +210,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { LocalBluetoothLeBroadcast(Context context) { mExecutor = Executors.newSingleThreadExecutor(); - BluetoothAdapter.getDefaultAdapter(). - getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); mBuilder = new BluetoothLeAudioContentMetadata.Builder(); mContentResolver = context.getContentResolver(); Handler handler = new Handler(Looper.getMainLooper()); mSettingsObserver = new BroadcastSettingsObserver(handler); updateBroadcastInfoFromContentProvider(); + + // Before registering callback, the constructor should finish creating the all of variables. + BluetoothAdapter.getDefaultAdapter() + .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 1be9d76cf3eb..39034047d6eb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -59,14 +59,14 @@ public class BluetoothMediaDevice extends MediaDevice { @Override public Drawable getIcon() { - return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) + return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) : BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first; } @Override public Drawable getIconWithoutBackground() { - return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) + return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) : BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first; } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java new file mode 100644 index 000000000000..0d6e91183a34 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.mobile.dataservice; + +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.UiccCardInfo; +import android.telephony.UiccPortInfo; +import android.telephony.UiccSlotInfo; +import android.telephony.UiccSlotMapping; + +public class DataServiceUtils { + + /** + * Represents columns of the MobileNetworkInfoData table, define these columns from + * {@see MobileNetworkUtils} or relevant common APIs. + */ + public static final class MobileNetworkInfoData { + + /** The name of the MobileNetworkInfoData table. */ + public static final String TABLE_NAME = "MobileNetworkInfo"; + + /** + * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()} + * as the primary key. + */ + public static final String COLUMN_ID = "subId"; + + /** + * The name of the WFC provision column, + * {@see MobileNetworkUtils#isWfcProvisionedOnDevice(int)}. + */ + public static final String COLUMN_IS_WFC_PROVISIONED_ON_DEVICE = "isWfcProvisionedOnDevice"; + + /** + * The name of the contact discovery enabled state column, + * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}. + */ + public static final String COLUMN_IS_CONTACT_DISCOVERY_ENABLED = + "isContactDiscoveryEnabled"; + + /** + * The name of the contact discovery visible state column, + * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}. + */ + public static final String COLUMN_IS_CONTACT_DISCOVERY_VISIBLE = + "isContactDiscoveryVisible"; + + /** + * The name of the mobile network data state column, + * {@see MobileNetworkUtils#isMobileDataEnabled(Context)}. + */ + public static final String COLUMN_IS_MOBILE_DATA_ENABLED = "isMobileDataEnabled"; + + /** + * The name of the CDMA option state column, + * {@see MobileNetworkUtils#isCdmaOptions(Context, int)}. + */ + public static final String COLUMN_IS_CDMA_OPTIONS = "isCdmaOptions"; + + /** + * The name of the GSM option state column, + * {@see MobileNetworkUtils#isGsmOptions(Context, int)}. + */ + public static final String COLUMN_IS_GSM_OPTIONS = "isGsmOptions"; + + /** + * The name of the world mode state column, + * {@see MobileNetworkUtils#isWorldMode(Context, int)}. + */ + public static final String COLUMN_IS_WORLD_MODE = "isWorldMode"; + + /** + * The name of the display network select options state column, + * {@see MobileNetworkUtils#shouldDisplayNetworkSelectOptions(Context, int)}. + */ + public static final String COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS = + "shouldDisplayNetworkSelectOptions"; + + /** + * The name of the TDSCDMA supported state column, + * {@see MobileNetworkUtils#isTdscdmaSupported(Context, int)}. + */ + public static final String COLUMN_IS_TDSCDMA_SUPPORTED = "isTdscdmaSupported"; + + /** + * The name of the active network is cellular state column, + * {@see MobileNetworkUtils#activeNetworkIsCellular(Context)}. + */ + public static final String COLUMN_ACTIVE_NETWORK_IS_CELLULAR = "activeNetworkIsCellular"; + + /** + * The name of the show toggle for physicalSim state column, + * {@see SubscriptionUtil#showToggleForPhysicalSim(SubscriptionManager)}. + */ + public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim"; + + } + + /** + * Represents columns of the UiccInfoData table, define these columns from + * {@link android.telephony.UiccSlotInfo}, {@link android.telephony.UiccCardInfo}, + * {@link UiccSlotMapping} and {@link android.telephony.UiccPortInfo}.If columns of these 4 + * classes are changed, we should also update the table except PII data. + */ + public static final class UiccInfoData { + + /** The name of the UiccInfoData table. */ + public static final String TABLE_NAME = "uiccInfo"; + + /** + * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()} + * as the primary key. + */ + public static final String COLUMN_ID = "sudId"; + + /** + * The name of the physical slot index column, see + * {@link UiccSlotMapping#getPhysicalSlotIndex()}. + */ + public static final String COLUMN_PHYSICAL_SLOT_INDEX = "physicalSlotIndex"; + + /** + * The name of the logical slot index column, see + * {@link UiccSlotMapping#getLogicalSlotIndex()}. + */ + public static final String COLUMN_LOGICAL_SLOT_INDEX = "logicalSlotIndex"; + + /** + * The name of the card ID column, see {@link UiccCardInfo#getCardId()}. + */ + public static final String COLUMN_CARD_ID = "cardId"; + + /** + * The name of the eUICC state column, see {@link UiccCardInfo#isEuicc()}. + */ + public static final String COLUMN_IS_EUICC = "isEuicc"; + + /** + * The name of the multiple enabled profiles supported state column, see + * {@link UiccCardInfo#isMultipleEnabledProfilesSupported()}. + */ + public static final String COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED = + "isMultipleEnabledProfilesSupported"; + + /** + * The name of the card state column, see {@link UiccSlotInfo#getCardStateInfo()}. + */ + public static final String COLUMN_CARD_STATE = "cardState"; + + /** + * The name of the extended APDU supported state column, see + * {@link UiccSlotInfo#getIsExtendedApduSupported()}. + */ + public static final String COLUMN_IS_EXTENDED_APDU_SUPPORTED = "isExtendedApduSupported"; + + /** + * The name of the removable state column, see {@link UiccSlotInfo#isRemovable()}. + */ + public static final String COLUMN_IS_REMOVABLE = "isRemovable"; + + /** + * The name of the active state column, see {@link UiccPortInfo#isActive()}. + */ + public static final String COLUMN_IS_ACTIVE = "isActive"; + + /** + * The name of the port index column, see {@link UiccPortInfo#getPortIndex()}. + */ + public static final String COLUMN_PORT_INDEX = "portIndex"; + } + + /** + * Represents columns of the SubscriptionInfoData table, define these columns from + * {@link SubscriptionInfo}, {@see SubscriptionUtil} and + * {@link SubscriptionManager} or relevant common APIs. If columns of the + * {@link SubscriptionInfo} are changed, we should also update the table except PII data. + */ + public static final class SubscriptionInfoData { + + /** The name of the SubscriptionInfoData table. */ + public static final String TABLE_NAME = "subscriptionInfo"; + + /** + * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()} + * as the primary key. + */ + public static final String COLUMN_ID = "sudId"; + + /** + * The name of the sim slot index column, see + * {@link SubscriptionInfo#getSimSlotIndex()}. + */ + public static final String COLUMN_SIM_SLOT_INDEX = "simSlotIndex"; + + /** + * The name of the carrier ID column, see {@link SubscriptionInfo#getCarrierId()}. + */ + public static final String COLUMN_CARRIER_ID = "carrierId"; + + /** + * The name of the display name column, see {@link SubscriptionInfo#getDisplayName()}. + */ + public static final String COLUMN_DISPLAY_NAME = "displayName"; + + /** + * The name of the carrier name column, see {@link SubscriptionInfo#getCarrierName()}. + */ + public static final String COLUMN_CARRIER_NAME = "carrierName"; + + /** + * The name of the data roaming state column, see + * {@link SubscriptionInfo#getDataRoaming()}. + */ + public static final String COLUMN_DATA_ROAMING = "dataRoaming"; + + /** + * The name of the mcc column, see {@link SubscriptionInfo#getMccString()}. + */ + public static final String COLUMN_MCC = "mcc"; + + /** + * The name of the mnc column, see {@link SubscriptionInfo#getMncString()}. + */ + public static final String COLUMN_MNC = "mnc"; + + /** + * The name of the country ISO column, see {@link SubscriptionInfo#getCountryIso()}. + */ + public static final String COLUMN_COUNTRY_ISO = "countryIso"; + + /** + * The name of the embedded state column, see {@link SubscriptionInfo#isEmbedded()}. + */ + public static final String COLUMN_IS_EMBEDDED = "isEmbedded"; + + /** + * The name of the card ID column, see {@link SubscriptionInfo#getCardId()}. + */ + public static final String COLUMN_CARD_ID = "cardId"; + + /** + * The name of the port index column, see {@link SubscriptionInfo#getPortIndex()}. + */ + public static final String COLUMN_PORT_INDEX = "portIndex"; + + /** + * The name of the opportunistic state column, see + * {@link SubscriptionInfo#isOpportunistic()}. + */ + public static final String COLUMN_IS_OPPORTUNISTIC = "isOpportunistic"; + + /** + * The name of the groupUUID column, see {@link SubscriptionInfo#getGroupUuid()}. + */ + public static final String COLUMN_GROUP_UUID = "groupUUID"; + + /** + * The name of the subscription type column, see + * {@link SubscriptionInfo#getSubscriptionType()}}. + */ + public static final String COLUMN_SUBSCRIPTION_TYPE = "subscriptionType"; + + /** + * The name of the uniqueName column, + * {@see SubscriptionUtil#getUniqueSubscriptionDisplayName(SubscriptionInfo, Context)}. + */ + public static final String COLUMN_UNIQUE_NAME = "uniqueName"; + + /** + * The name of the subscription visible state column, + * {@see SubscriptionUtil#isSubscriptionVisible(SubscriptionManager, Context, + * SubscriptionInfo)}. + */ + public static final String COLUMN_IS_SUBSCRIPTION_VISIBLE = "isSubscriptionVisible"; + + /** + * The name of the formatted phone number column, + * {@see SubscriptionUtil#getFormattedPhoneNumber(Context, SubscriptionInfo)}. + */ + public static final String COLUMN_FORMATTED_PHONE_NUMBER = "getFormattedPhoneNumber"; + + /** + * The name of the first removable subscription state column, + * {@see SubscriptionUtil#getFirstRemovableSubscription(Context)}. + */ + public static final String COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION = + "isFirstRemovableSubscription"; + + /** + * The name of the default SIM config column, + * {@see SubscriptionUtil#getDefaultSimConfig(Context, int)}. + */ + public static final String COLUMN_DEFAULT_SIM_CONFIG = "defaultSimConfig"; + + /** + * The name of the default subscription selection column, + * {@see SubscriptionUtil#getSubscriptionOrDefault(Context, int)}. + */ + public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION = + "isDefaultSubscriptionSelection"; + + /** + * The name of the valid subscription column, + * {@link SubscriptionManager#isValidSubscriptionId(int)}. + */ + public static final String COLUMN_IS_VALID_SUBSCRIPTION = "isValidSubscription"; + + /** + * The name of the usable subscription column, + * {@link SubscriptionManager#isUsableSubscriptionId(int)}. + */ + public static final String COLUMN_IS_USABLE_SUBSCRIPTION = "isUsableSubscription"; + + /** + * The name of the active subscription column, + * {@link SubscriptionManager#isActiveSubscriptionId(int)}. + */ + public static final String COLUMN_IS_ACTIVE_SUBSCRIPTION_ID = "isActiveSubscription"; + + /** + * The name of the available subscription column, + * {@see SubscriptionUtil#getAvailableSubscription(Context, ProxySubscriptionManager, int)}. + */ + public static final String COLUMN_IS_AVAILABLE_SUBSCRIPTION = "isAvailableSubscription"; + + /** + * The name of the default voice subscription state column, see + * {@link SubscriptionManager#getDefaultVoiceSubscriptionId()}. + */ + public static final String COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION = + "isDefaultVoiceSubscription"; + + /** + * The name of the default sms subscription state column, see + * {@link SubscriptionManager#getDefaultSmsSubscriptionId()}. + */ + public static final String COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION = "isDefaultSmsSubscription"; + + /** + * The name of the default data subscription state column, see + * {@link SubscriptionManager#getDefaultDataSubscriptionId()}. + */ + public static final String COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION = + "isDefaultDataSubscription"; + + /** + * The name of the default subscription state column, see + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + */ + public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription"; + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 1c0ea1a1f4b0..ca14573c95e1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -205,4 +205,45 @@ public class BluetoothUtilsTest { public void isAdvancedDetailsHeader_noMetadata_returnFalse() { assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false); } + + @Test + public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( + BOOL_METADATA.getBytes()); + + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true); + } + + @Test + public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn( + BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes()); + + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true); + } + + @Test + public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn( + BluetoothDevice.DEVICE_TYPE_WATCH.getBytes()); + + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false); + } + + @Test + public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn( + BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes()); + + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false); + } + + @Test + public void isAdvancedUntetheredDevice_noMetadata_returnFalse() { + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false); + } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f05f63700710..b1979c905510 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -768,6 +768,7 @@ public class SettingsBackupTest { Settings.Secure.SMS_DEFAULT_APPLICATION, Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q Settings.Secure.TRUST_AGENTS_INITIALIZED, + Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS, Settings.Secure.TV_INPUT_CUSTOM_LABELS, Settings.Secure.TV_INPUT_HIDDEN_INPUTS, diff --git a/packages/Shell/res/values-ro/strings.xml b/packages/Shell/res/values-ro/strings.xml index 91b0b1e4cd59..56e9ee0d1350 100644 --- a/packages/Shell/res/values-ro/strings.xml +++ b/packages/Shell/res/values-ro/strings.xml @@ -21,14 +21,14 @@ <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Raportul de eroare <xliff:g id="ID">#%d</xliff:g> se generează"</string> <string name="bugreport_finished_title" msgid="4429132808670114081">"Raportul de eroare <xliff:g id="ID">#%d</xliff:g> a fost creat"</string> <string name="bugreport_updating_title" msgid="4423539949559634214">"Se adaugă detaliile la raportul de eroare"</string> - <string name="bugreport_updating_wait" msgid="3322151947853929470">"Așteptați…"</string> + <string name="bugreport_updating_wait" msgid="3322151947853929470">"Te rugăm să aștepți…"</string> <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Raportul de eroare va apărea curând pe telefon"</string> - <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selectați pentru a trimite raportul de eroare"</string> - <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Atingeți pentru a trimite raportul de eroare"</string> - <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Selectați pentru a trimite raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string> - <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Atingeți ca să trimiteți raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string> - <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Atingeți ca să trimiteți raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string> - <string name="bugreport_confirm" msgid="5917407234515812495">"Rapoartele despre erori conțin date din diferite fișiere de jurnal ale sistemului. Acestea pot include date pe care le puteți considera sensibile (cum ar fi utilizarea aplicației și date despre locație). Permiteți accesul la rapoartele despre erori numai aplicațiilor și persoanelor în care aveți încredere."</string> + <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selectează pentru a trimite raportul de eroare"</string> + <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Atinge pentru a trimite raportul de eroare"</string> + <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Selectează pentru a trimite raportul fără captură de ecran sau așteaptă finalizarea acesteia"</string> + <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Atinge ca să trimiți raportul de eroare fără captură de ecran sau așteaptă finalizarea acesteia"</string> + <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Atinge ca să trimiți raportul de eroare fără captură de ecran sau așteaptă finalizarea acesteia"</string> + <string name="bugreport_confirm" msgid="5917407234515812495">"Rapoartele despre erori conțin date din diferite fișiere de jurnal ale sistemului. Acestea pot include date pe care le poți considera sensibile (cum ar fi utilizarea aplicației și date despre locație). Permite accesul la rapoartele despre erori numai aplicațiilor și persoanelor în care ai încredere."</string> <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Nu mai afișa"</string> <string name="bugreport_storage_title" msgid="5332488144740527109">"Rapoarte de erori"</string> <string name="bugreport_unreadable_text" msgid="586517851044535486">"Fișierul cu raportul de eroare nu a putut fi citit"</string> @@ -42,6 +42,6 @@ <string name="bugreport_info_name" msgid="4414036021935139527">"Numele fișierului"</string> <string name="bugreport_info_title" msgid="2306030793918239804">"Titlul erorii"</string> <string name="bugreport_info_description" msgid="5072835127481627722">"Rezumat privind eroarea"</string> - <string name="save" msgid="4781509040564835759">"Salvați"</string> - <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"Trimiteți raportul de eroare"</string> + <string name="save" msgid="4781509040564835759">"Salvează"</string> + <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"Trimite raportul de eroare"</string> </resources> diff --git a/packages/SimAppDialog/res/values-ro/strings.xml b/packages/SimAppDialog/res/values-ro/strings.xml index 21663d125f7f..5d876eac82d8 100644 --- a/packages/SimAppDialog/res/values-ro/strings.xml +++ b/packages/SimAppDialog/res/values-ro/strings.xml @@ -18,9 +18,9 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="8898068901680117589">"Sim App Dialog"</string> - <string name="install_carrier_app_title" msgid="334729104862562585">"Activați serviciul mobil"</string> + <string name="install_carrier_app_title" msgid="334729104862562585">"Activează serviciul mobil"</string> <string name="install_carrier_app_description" msgid="4014303558674923797">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalați aplicația <xliff:g id="ID_1">%1$s</xliff:g>"</string> <string name="install_carrier_app_description_default" msgid="7356830245205847840">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalați aplicația operatorului"</string> <string name="install_carrier_app_defer_action" msgid="2558576736886876209">"Nu acum"</string> - <string name="install_carrier_app_download_action" msgid="7859229305958538064">"Descărcați aplicația"</string> + <string name="install_carrier_app_download_action" msgid="7859229305958538064">"Descarcă aplicația"</string> </resources> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index b7fb4727e981..f3614d327561 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -124,6 +124,7 @@ android_library { "dagger2", "jsr330", "lottie", + "LowLightDreamLib", ], manifest: "AndroidManifest.xml", @@ -228,6 +229,7 @@ android_library { "dagger2", "jsr330", "WindowManager-Shell", + "LowLightDreamLib", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt new file mode 100644 index 000000000000..19624e605be8 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.layout.pager + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.rememberSplineBasedDecay +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter + +/** Library-wide switch to turn on debug logging. */ +internal const val DebugLog = false + +@RequiresOptIn(message = "Accompanist Pager is experimental. The API may be changed in the future.") +@Retention(AnnotationRetention.BINARY) +annotation class ExperimentalPagerApi + +/** Contains the default values used by [HorizontalPager] and [VerticalPager]. */ +@ExperimentalPagerApi +object PagerDefaults { + /** + * Remember the default [FlingBehavior] that represents the scroll curve. + * + * @param state The [PagerState] to update. + * @param decayAnimationSpec The decay animation spec to use for decayed flings. + * @param snapAnimationSpec The animation spec to use when snapping. + */ + @Composable + fun flingBehavior( + state: PagerState, + decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(), + snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec, + ): FlingBehavior = + rememberSnappingFlingBehavior( + lazyListState = state.lazyListState, + decayAnimationSpec = decayAnimationSpec, + snapAnimationSpec = snapAnimationSpec, + ) + + @Deprecated( + "Replaced with PagerDefaults.flingBehavior()", + ReplaceWith("PagerDefaults.flingBehavior(state, decayAnimationSpec, snapAnimationSpec)") + ) + @Composable + fun rememberPagerFlingConfig( + state: PagerState, + decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(), + snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec, + ): FlingBehavior = flingBehavior(state, decayAnimationSpec, snapAnimationSpec) +} + +/** + * A horizontally scrolling layout that allows users to flip between items to the left and right. + * + * @sample com.google.accompanist.sample.pager.HorizontalPagerSample + * + * @param count the number of pages. + * @param modifier the modifier to apply to this layout. + * @param state the state object to be used to control or observe the pager's state. + * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be + * composed from the end to the start and [PagerState.currentPage] == 0 will mean the first item is + * located at the end. + * @param itemSpacing horizontal spacing to add between items. + * @param flingBehavior logic describing fling behavior. + * @param key the scroll position will be maintained based on the key, which means if you add/remove + * items before the current visible item the item with the given key will be kept as the first + * visible one. + * @param content a block which describes the content. Inside this block you can reference + * [PagerScope.currentPage] and other properties in [PagerScope]. + */ +@ExperimentalPagerApi +@Composable +fun HorizontalPager( + count: Int, + modifier: Modifier = Modifier, + state: PagerState = rememberPagerState(), + reverseLayout: Boolean = false, + itemSpacing: Dp = 0.dp, + flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state), + verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, + key: ((page: Int) -> Any)? = null, + contentPadding: PaddingValues = PaddingValues(0.dp), + content: @Composable PagerScope.(page: Int) -> Unit, +) { + Pager( + count = count, + state = state, + modifier = modifier, + isVertical = false, + reverseLayout = reverseLayout, + itemSpacing = itemSpacing, + verticalAlignment = verticalAlignment, + flingBehavior = flingBehavior, + key = key, + contentPadding = contentPadding, + content = content + ) +} + +/** + * A vertically scrolling layout that allows users to flip between items to the top and bottom. + * + * @sample com.google.accompanist.sample.pager.VerticalPagerSample + * + * @param count the number of pages. + * @param modifier the modifier to apply to this layout. + * @param state the state object to be used to control or observe the pager's state. + * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be + * composed from the bottom to the top and [PagerState.currentPage] == 0 will mean the first item is + * located at the bottom. + * @param itemSpacing vertical spacing to add between items. + * @param flingBehavior logic describing fling behavior. + * @param key the scroll position will be maintained based on the key, which means if you add/remove + * items before the current visible item the item with the given key will be kept as the first + * visible one. + * @param content a block which describes the content. Inside this block you can reference + * [PagerScope.currentPage] and other properties in [PagerScope]. + */ +@ExperimentalPagerApi +@Composable +fun VerticalPager( + count: Int, + modifier: Modifier = Modifier, + state: PagerState = rememberPagerState(), + reverseLayout: Boolean = false, + itemSpacing: Dp = 0.dp, + flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state), + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + key: ((page: Int) -> Any)? = null, + contentPadding: PaddingValues = PaddingValues(0.dp), + content: @Composable PagerScope.(page: Int) -> Unit, +) { + Pager( + count = count, + state = state, + modifier = modifier, + isVertical = true, + reverseLayout = reverseLayout, + itemSpacing = itemSpacing, + horizontalAlignment = horizontalAlignment, + flingBehavior = flingBehavior, + key = key, + contentPadding = contentPadding, + content = content + ) +} + +@ExperimentalPagerApi +@Composable +internal fun Pager( + count: Int, + modifier: Modifier, + state: PagerState, + reverseLayout: Boolean, + itemSpacing: Dp, + isVertical: Boolean, + flingBehavior: FlingBehavior, + key: ((page: Int) -> Any)?, + contentPadding: PaddingValues, + verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + content: @Composable PagerScope.(page: Int) -> Unit, +) { + require(count >= 0) { "pageCount must be >= 0" } + + // Provide our PagerState with access to the SnappingFlingBehavior animation target + // TODO: can this be done in a better way? + state.flingAnimationTarget = { (flingBehavior as? SnappingFlingBehavior)?.animationTarget } + + LaunchedEffect(count) { + state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0) + } + + // Once a fling (scroll) has finished, notify the state + LaunchedEffect(state) { + // When a 'scroll' has finished, notify the state + snapshotFlow { state.isScrollInProgress } + .filter { !it } + .collect { state.onScrollFinished() } + } + + val pagerScope = remember(state) { PagerScopeImpl(state) } + + // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate + // as normal + val consumeFlingNestedScrollConnection = + ConsumeFlingNestedScrollConnection( + consumeHorizontal = !isVertical, + consumeVertical = isVertical, + ) + + if (isVertical) { + LazyColumn( + state = state.lazyListState, + verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment), + horizontalAlignment = horizontalAlignment, + flingBehavior = flingBehavior, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + modifier = modifier, + ) { + items( + count = count, + key = key, + ) { page -> + Box( + Modifier + // We don't any nested flings to continue in the pager, so we add a + // connection which consumes them. + // See: https://github.com/google/accompanist/issues/347 + .nestedScroll(connection = consumeFlingNestedScrollConnection) + // Constraint the content to be <= than the size of the pager. + .fillParentMaxHeight() + .wrapContentSize() + ) { pagerScope.content(page) } + } + } + } else { + LazyRow( + state = state.lazyListState, + verticalAlignment = verticalAlignment, + horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment), + flingBehavior = flingBehavior, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + modifier = modifier, + ) { + items( + count = count, + key = key, + ) { page -> + Box( + Modifier + // We don't any nested flings to continue in the pager, so we add a + // connection which consumes them. + // See: https://github.com/google/accompanist/issues/347 + .nestedScroll(connection = consumeFlingNestedScrollConnection) + // Constraint the content to be <= than the size of the pager. + .fillParentMaxWidth() + .wrapContentSize() + ) { pagerScope.content(page) } + } + } + } +} + +private class ConsumeFlingNestedScrollConnection( + private val consumeHorizontal: Boolean, + private val consumeVertical: Boolean, +) : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset = + when (source) { + // We can consume all resting fling scrolls so that they don't propagate up to the + // Pager + NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical) + else -> Offset.Zero + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // We can consume all post fling velocity on the main-axis + // so that it doesn't propagate up to the Pager + return available.consume(consumeHorizontal, consumeVertical) + } +} + +private fun Offset.consume( + consumeHorizontal: Boolean, + consumeVertical: Boolean, +): Offset = + Offset( + x = if (consumeHorizontal) this.x else 0f, + y = if (consumeVertical) this.y else 0f, + ) + +private fun Velocity.consume( + consumeHorizontal: Boolean, + consumeVertical: Boolean, +): Velocity = + Velocity( + x = if (consumeHorizontal) this.x else 0f, + y = if (consumeVertical) this.y else 0f, + ) + +/** Scope for [HorizontalPager] content. */ +@ExperimentalPagerApi +@Stable +interface PagerScope { + /** Returns the current selected page */ + val currentPage: Int + + /** The current offset from the start of [currentPage], as a ratio of the page width. */ + val currentPageOffset: Float +} + +@ExperimentalPagerApi +private class PagerScopeImpl( + private val state: PagerState, +) : PagerScope { + override val currentPage: Int + get() = state.currentPage + override val currentPageOffset: Float + get() = state.currentPageOffset +} + +/** + * Calculate the offset for the given [page] from the current scroll position. This is useful when + * using the scroll position to apply effects or animations to items. + * + * The returned offset can positive or negative, depending on whether which direction the [page] is + * compared to the current scroll position. + * + * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition + */ +@ExperimentalPagerApi +fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float { + return (currentPage + currentPageOffset) - page +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt new file mode 100644 index 000000000000..288c26eb1199 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.layout.pager + +import androidx.annotation.FloatRange +import androidx.annotation.IntRange +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.spring +import androidx.compose.foundation.MutatePriority +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import kotlin.math.absoluteValue +import kotlin.math.roundToInt + +@Deprecated( + "Replaced with rememberPagerState(initialPage) and count parameter on Pager composables", + ReplaceWith("rememberPagerState(initialPage)"), + level = DeprecationLevel.ERROR, +) +@Suppress("UNUSED_PARAMETER", "NOTHING_TO_INLINE") +@ExperimentalPagerApi +@Composable +inline fun rememberPagerState( + @IntRange(from = 0) pageCount: Int, + @IntRange(from = 0) initialPage: Int = 0, + @FloatRange(from = 0.0, to = 1.0) initialPageOffset: Float = 0f, + @IntRange(from = 1) initialOffscreenLimit: Int = 1, + infiniteLoop: Boolean = false +): PagerState { + return rememberPagerState(initialPage = initialPage) +} + +/** + * Creates a [PagerState] that is remembered across compositions. + * + * Changes to the provided values for [initialPage] will **not** result in the state being recreated + * or changed in any way if it has already been created. + * + * @param initialPage the initial value for [PagerState.currentPage] + */ +@ExperimentalPagerApi +@Composable +fun rememberPagerState( + @IntRange(from = 0) initialPage: Int = 0, +): PagerState = + rememberSaveable(saver = PagerState.Saver) { + PagerState( + currentPage = initialPage, + ) + } + +/** + * A state object that can be hoisted to control and observe scrolling for [HorizontalPager]. + * + * In most cases, this will be created via [rememberPagerState]. + * + * @param currentPage the initial value for [PagerState.currentPage] + */ +@ExperimentalPagerApi +@Stable +class PagerState( + @IntRange(from = 0) currentPage: Int = 0, +) : ScrollableState { + // Should this be public? + internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage) + + private var _currentPage by mutableStateOf(currentPage) + + private val currentLayoutPageInfo: LazyListItemInfo? + get() = + lazyListState.layoutInfo.visibleItemsInfo + .asSequence() + .filter { it.offset <= 0 && it.offset + it.size > 0 } + .lastOrNull() + + private val currentLayoutPageOffset: Float + get() = + currentLayoutPageInfo?.let { current -> + // We coerce since itemSpacing can make the offset > 1f. + // We don't want to count spacing in the offset so cap it to 1f + (-current.offset / current.size.toFloat()).coerceIn(0f, 1f) + } + ?: 0f + + /** + * [InteractionSource] that will be used to dispatch drag events when this list is being + * dragged. If you want to know whether the fling (or animated scroll) is in progress, use + * [isScrollInProgress]. + */ + val interactionSource: InteractionSource + get() = lazyListState.interactionSource + + /** The number of pages to display. */ + @get:IntRange(from = 0) + val pageCount: Int by derivedStateOf { lazyListState.layoutInfo.totalItemsCount } + + /** + * The index of the currently selected page. This may not be the page which is currently + * displayed on screen. + * + * To update the scroll position, use [scrollToPage] or [animateScrollToPage]. + */ + @get:IntRange(from = 0) + var currentPage: Int + get() = _currentPage + internal set(value) { + if (value != _currentPage) { + _currentPage = value + } + } + + /** + * The current offset from the start of [currentPage], as a ratio of the page width. + * + * To update the scroll position, use [scrollToPage] or [animateScrollToPage]. + */ + val currentPageOffset: Float by derivedStateOf { + currentLayoutPageInfo?.let { + // The current page offset is the current layout page delta from `currentPage` + // (which is only updated after a scroll/animation). + // We calculate this by looking at the current layout page + it's offset, + // then subtracting the 'current page'. + it.index + currentLayoutPageOffset - _currentPage + } + ?: 0f + } + + /** The target page for any on-going animations. */ + private var animationTargetPage: Int? by mutableStateOf(null) + + internal var flingAnimationTarget: (() -> Int?)? by mutableStateOf(null) + + /** + * The target page for any on-going animations or scrolls by the user. Returns the current page + * if a scroll or animation is not currently in progress. + */ + val targetPage: Int + get() = + animationTargetPage + ?: flingAnimationTarget?.invoke() + ?: when { + // If a scroll isn't in progress, return the current page + !isScrollInProgress -> currentPage + // If the offset is 0f (or very close), return the current page + currentPageOffset.absoluteValue < 0.001f -> currentPage + // If we're offset towards the start, guess the previous page + currentPageOffset < -0.5f -> (currentPage - 1).coerceAtLeast(0) + // If we're offset towards the end, guess the next page + else -> (currentPage + 1).coerceAtMost(pageCount - 1) + } + + @Deprecated( + "Replaced with animateScrollToPage(page, pageOffset)", + ReplaceWith("animateScrollToPage(page = page, pageOffset = pageOffset)") + ) + @Suppress("UNUSED_PARAMETER") + suspend fun animateScrollToPage( + @IntRange(from = 0) page: Int, + @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f, + animationSpec: AnimationSpec<Float> = spring(), + initialVelocity: Float = 0f, + skipPages: Boolean = true, + ) { + animateScrollToPage(page = page, pageOffset = pageOffset) + } + + /** + * Animate (smooth scroll) to the given page to the middle of the viewport. + * + * Cancels the currently running scroll, if any, and suspends until the cancellation is + * complete. + * + * @param page the page to animate to. Must be between 0 and [pageCount] (inclusive). + * @param pageOffset the percentage of the page width to offset, from the start of [page]. Must + * be in the range 0f..1f. + */ + suspend fun animateScrollToPage( + @IntRange(from = 0) page: Int, + @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f, + ) { + requireCurrentPage(page, "page") + requireCurrentPageOffset(pageOffset, "pageOffset") + try { + animationTargetPage = page + + if (pageOffset <= 0.005f) { + // If the offset is (close to) zero, just call animateScrollToItem and we're done + lazyListState.animateScrollToItem(index = page) + } else { + // Else we need to figure out what the offset is in pixels... + + var target = + lazyListState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == page } + + if (target != null) { + // If we have access to the target page layout, we can calculate the pixel + // offset from the size + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (target.size * pageOffset).roundToInt() + ) + } else { + // If we don't, we use the current page size as a guide + val currentSize = currentLayoutPageInfo!!.size + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (currentSize * pageOffset).roundToInt() + ) + + // The target should be visible now + target = lazyListState.layoutInfo.visibleItemsInfo.first { it.index == page } + + if (target.size != currentSize) { + // If the size we used for calculating the offset differs from the actual + // target page size, we need to scroll again. This doesn't look great, + // but there's not much else we can do. + lazyListState.animateScrollToItem( + index = page, + scrollOffset = (target.size * pageOffset).roundToInt() + ) + } + } + } + } finally { + // We need to manually call this, as the `animateScrollToItem` call above will happen + // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect + // the change. This is especially true when running unit tests. + onScrollFinished() + } + } + + /** + * Instantly brings the item at [page] to the middle of the viewport. + * + * Cancels the currently running scroll, if any, and suspends until the cancellation is + * complete. + * + * @param page the page to snap to. Must be between 0 and [pageCount] (inclusive). + */ + suspend fun scrollToPage( + @IntRange(from = 0) page: Int, + @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f, + ) { + requireCurrentPage(page, "page") + requireCurrentPageOffset(pageOffset, "pageOffset") + try { + animationTargetPage = page + + // First scroll to the given page. It will now be laid out at offset 0 + lazyListState.scrollToItem(index = page) + + // If we have a start spacing, we need to offset (scroll) by that too + if (pageOffset > 0.0001f) { + scroll { currentLayoutPageInfo?.let { scrollBy(it.size * pageOffset) } } + } + } finally { + // We need to manually call this, as the `scroll` call above will happen in 1 frame, + // which is usually too fast for the LaunchedEffect in Pager to detect the change. + // This is especially true when running unit tests. + onScrollFinished() + } + } + + internal fun onScrollFinished() { + // Then update the current page to our layout page + currentPage = currentLayoutPageInfo?.index ?: 0 + // Clear the animation target page + animationTargetPage = null + } + + override suspend fun scroll( + scrollPriority: MutatePriority, + block: suspend ScrollScope.() -> Unit + ) = lazyListState.scroll(scrollPriority, block) + + override fun dispatchRawDelta(delta: Float): Float { + return lazyListState.dispatchRawDelta(delta) + } + + override val isScrollInProgress: Boolean + get() = lazyListState.isScrollInProgress + + override fun toString(): String = + "PagerState(" + + "pageCount=$pageCount, " + + "currentPage=$currentPage, " + + "currentPageOffset=$currentPageOffset" + + ")" + + private fun requireCurrentPage(value: Int, name: String) { + if (pageCount == 0) { + require(value == 0) { "$name must be 0 when pageCount is 0" } + } else { + require(value in 0 until pageCount) { "$name[$value] must be >= 0 and < pageCount" } + } + } + + private fun requireCurrentPageOffset(value: Float, name: String) { + if (pageCount == 0) { + require(value == 0f) { "$name must be 0f when pageCount is 0" } + } else { + require(value in 0f..1f) { "$name must be >= 0 and <= 1" } + } + } + + companion object { + /** The default [Saver] implementation for [PagerState]. */ + val Saver: Saver<PagerState, *> = + listSaver( + save = { + listOf<Any>( + it.currentPage, + ) + }, + restore = { + PagerState( + currentPage = it[0] as Int, + ) + } + ) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt new file mode 100644 index 000000000000..0b53f5324a7d --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.layout.pager + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.AnimationState +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.animateDecay +import androidx.compose.animation.core.animateTo +import androidx.compose.animation.core.calculateTargetValue +import androidx.compose.animation.core.spring +import androidx.compose.animation.rememberSplineBasedDecay +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import kotlin.math.abs + +/** Default values used for [SnappingFlingBehavior] & [rememberSnappingFlingBehavior]. */ +internal object SnappingFlingBehaviorDefaults { + /** TODO */ + val snapAnimationSpec: AnimationSpec<Float> = spring(stiffness = 600f) +} + +/** + * Create and remember a snapping [FlingBehavior] to be used with [LazyListState]. + * + * TODO: move this to a new module and make it public + * + * @param lazyListState The [LazyListState] to update. + * @param decayAnimationSpec The decay animation spec to use for decayed flings. + * @param snapAnimationSpec The animation spec to use when snapping. + */ +@Composable +internal fun rememberSnappingFlingBehavior( + lazyListState: LazyListState, + decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(), + snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec, +): SnappingFlingBehavior = + remember(lazyListState, decayAnimationSpec, snapAnimationSpec) { + SnappingFlingBehavior( + lazyListState = lazyListState, + decayAnimationSpec = decayAnimationSpec, + snapAnimationSpec = snapAnimationSpec, + ) + } + +/** + * A snapping [FlingBehavior] for [LazyListState]. Typically this would be created via + * [rememberSnappingFlingBehavior]. + * + * @param lazyListState The [LazyListState] to update. + * @param decayAnimationSpec The decay animation spec to use for decayed flings. + * @param snapAnimationSpec The animation spec to use when snapping. + */ +internal class SnappingFlingBehavior( + private val lazyListState: LazyListState, + private val decayAnimationSpec: DecayAnimationSpec<Float>, + private val snapAnimationSpec: AnimationSpec<Float>, +) : FlingBehavior { + /** The target item index for any on-going animations. */ + var animationTarget: Int? by mutableStateOf(null) + private set + + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + val itemInfo = currentItemInfo ?: return initialVelocity + + // If the decay fling can scroll past the current item, fling with decay + return if (decayAnimationSpec.canFlingPastCurrentItem(itemInfo, initialVelocity)) { + performDecayFling(initialVelocity, itemInfo) + } else { + // Otherwise we 'spring' to current/next item + performSpringFling( + index = + when { + // If the velocity is greater than 1 item per second (velocity is px/s), + // spring + // in the relevant direction + initialVelocity > itemInfo.size -> { + (itemInfo.index + 1).coerceAtMost( + lazyListState.layoutInfo.totalItemsCount - 1 + ) + } + initialVelocity < -itemInfo.size -> itemInfo.index + // If the velocity is 0 (or less than the size of the item), spring to + // whichever item is closest to the snap point + itemInfo.offset < -itemInfo.size / 2 -> itemInfo.index + 1 + else -> itemInfo.index + }, + initialVelocity = initialVelocity, + ) + } + } + + private suspend fun ScrollScope.performDecayFling( + initialVelocity: Float, + startItem: LazyListItemInfo, + ): Float { + val index = + when { + initialVelocity > 0 -> startItem.index + 1 + else -> startItem.index + } + val forward = index > (currentItemInfo?.index ?: return initialVelocity) + + // Update the animationTarget + animationTarget = index + + var velocityLeft = initialVelocity + var lastValue = 0f + AnimationState( + initialValue = 0f, + initialVelocity = initialVelocity, + ) + .animateDecay(decayAnimationSpec) { + val delta = value - lastValue + val consumed = scrollBy(delta) + lastValue = value + velocityLeft = this.velocity + + val current = currentItemInfo + if (current == null) { + cancelAnimation() + return@animateDecay + } + + if ( + !forward && + (current.index < index || current.index == index && current.offset >= 0) + ) { + // 'snap back' to the item as we may have scrolled past it + scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat()) + cancelAnimation() + } else if ( + forward && + (current.index > index || current.index == index && current.offset <= 0) + ) { + // 'snap back' to the item as we may have scrolled past it + scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat()) + cancelAnimation() + } else if (abs(delta - consumed) > 0.5f) { + // avoid rounding errors and stop if anything is unconsumed + cancelAnimation() + } + } + animationTarget = null + return velocityLeft + } + + private suspend fun ScrollScope.performSpringFling( + index: Int, + scrollOffset: Int = 0, + initialVelocity: Float = 0f, + ): Float { + // If we don't have a current layout, we can't snap + val initialItem = currentItemInfo ?: return initialVelocity + + val forward = index > initialItem.index + // We add 10% on to the size of the current item, to compensate for any item spacing, etc + val target = (if (forward) initialItem.size else -initialItem.size) * 1.1f + + // Update the animationTarget + animationTarget = index + + var velocityLeft = initialVelocity + var lastValue = 0f + AnimationState( + initialValue = 0f, + initialVelocity = initialVelocity, + ) + .animateTo( + targetValue = target, + animationSpec = snapAnimationSpec, + ) { + // Springs can overshoot their target, clamp to the desired range + val coercedValue = + if (forward) { + value.coerceAtMost(target) + } else { + value.coerceAtLeast(target) + } + val delta = coercedValue - lastValue + val consumed = scrollBy(delta) + lastValue = coercedValue + velocityLeft = this.velocity + + val current = currentItemInfo + if (current == null) { + cancelAnimation() + return@animateTo + } + + if (scrolledPastItem(initialVelocity, current, index, scrollOffset)) { + // If we've scrolled to/past the item, stop the animation. We may also need to + // 'snap back' to the item as we may have scrolled past it + scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat()) + cancelAnimation() + } else if (abs(delta - consumed) > 0.5f) { + // avoid rounding errors and stop if anything is unconsumed + cancelAnimation() + } + } + animationTarget = null + return velocityLeft + } + + private fun LazyListState.calculateScrollOffsetToItem(index: Int): Int { + return layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }?.offset ?: 0 + } + + private val currentItemInfo: LazyListItemInfo? + get() = + lazyListState.layoutInfo.visibleItemsInfo + .asSequence() + .filter { it.offset <= 0 && it.offset + it.size > 0 } + .lastOrNull() +} + +private fun scrolledPastItem( + initialVelocity: Float, + currentItem: LazyListItemInfo, + targetIndex: Int, + targetScrollOffset: Int = 0, +): Boolean { + return if (initialVelocity > 0) { + // forward + currentItem.index > targetIndex || + (currentItem.index == targetIndex && currentItem.offset <= targetScrollOffset) + } else { + // backwards + currentItem.index < targetIndex || + (currentItem.index == targetIndex && currentItem.offset >= targetScrollOffset) + } +} + +private fun DecayAnimationSpec<Float>.canFlingPastCurrentItem( + currentItem: LazyListItemInfo, + initialVelocity: Float, +): Boolean { + val targetValue = + calculateTargetValue( + initialValue = currentItem.offset.toFloat(), + initialVelocity = initialVelocity, + ) + return when { + // forward. We add 10% onto the size to cater for any item spacing + initialVelocity > 0 -> targetValue <= -(currentItem.size * 1.1f) + // backwards. We add 10% onto the size to cater for any item spacing + else -> targetValue >= (currentItem.size * 0.1f) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt new file mode 100644 index 000000000000..3b13c0b78cbe --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.InspectorValueInfo +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.constrainHeight +import androidx.compose.ui.unit.constrainWidth +import androidx.compose.ui.unit.offset + +// This file was mostly copy/pasted from by androidx.compose.foundation.layout.Padding.kt and +// contains modifiers with lambda parameters to change the padding of a Composable without +// triggering recomposition when the paddings change. +// +// These should be used instead of the traditional size modifiers when the size changes often, for +// instance when it is animated. +// +// TODO(b/247473910): Remove these modifiers once they can be fully replaced by layout animations +// APIs. + +/** @see androidx.compose.foundation.layout.padding */ +fun Modifier.padding( + start: Density.() -> Int = PaddingUnspecified, + top: Density.() -> Int = PaddingUnspecified, + end: Density.() -> Int = PaddingUnspecified, + bottom: Density.() -> Int = PaddingUnspecified, +) = + this.then( + PaddingModifier( + start, + top, + end, + bottom, + rtlAware = true, + inspectorInfo = + debugInspectorInfo { + name = "padding" + properties["start"] = start + properties["top"] = top + properties["end"] = end + properties["bottom"] = bottom + } + ) + ) + +/** @see androidx.compose.foundation.layout.padding */ +fun Modifier.padding( + horizontal: Density.() -> Int = PaddingUnspecified, + vertical: Density.() -> Int = PaddingUnspecified, +): Modifier { + return this.then( + PaddingModifier( + start = horizontal, + top = vertical, + end = horizontal, + bottom = vertical, + rtlAware = true, + inspectorInfo = + debugInspectorInfo { + name = "padding" + properties["horizontal"] = horizontal + properties["vertical"] = vertical + } + ) + ) +} + +private val PaddingUnspecified: Density.() -> Int = { 0 } + +private class PaddingModifier( + val start: Density.() -> Int, + val top: Density.() -> Int, + val end: Density.() -> Int, + val bottom: Density.() -> Int, + val rtlAware: Boolean, + inspectorInfo: InspectorInfo.() -> Unit +) : LayoutModifier, InspectorValueInfo(inspectorInfo) { + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val start = start() + val top = top() + val end = end() + val bottom = bottom() + + val horizontal = start + end + val vertical = top + bottom + + val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) + + val width = constraints.constrainWidth(placeable.width + horizontal) + val height = constraints.constrainHeight(placeable.height + vertical) + return layout(width, height) { + if (rtlAware) { + placeable.placeRelative(start, top) + } else { + placeable.place(start, top) + } + } + } + + override fun hashCode(): Int { + var result = start.hashCode() + result = 31 * result + top.hashCode() + result = 31 * result + end.hashCode() + result = 31 * result + bottom.hashCode() + result = 31 * result + rtlAware.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + val otherModifier = other as? PaddingModifier ?: return false + return start == otherModifier.start && + top == otherModifier.top && + end == otherModifier.end && + bottom == otherModifier.bottom && + rtlAware == otherModifier.rtlAware + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt new file mode 100644 index 000000000000..570d24312c80 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.compose.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.IntrinsicMeasurable +import androidx.compose.ui.layout.IntrinsicMeasureScope +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.InspectorValueInfo +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.constrain +import androidx.compose.ui.unit.constrainHeight +import androidx.compose.ui.unit.constrainWidth + +// This file was mostly copy pasted from androidx.compose.foundation.layout.Size.kt and contains +// modifiers with lambda parameters to change the (min/max) size of a Composable without triggering +// recomposition when the sizes change. +// +// These should be used instead of the traditional size modifiers when the size changes often, for +// instance when it is animated. +// +// TODO(b/247473910): Remove these modifiers once they can be fully replaced by layout animations +// APIs. + +/** @see androidx.compose.foundation.layout.width */ +fun Modifier.width(width: Density.() -> Int) = + this.then( + SizeModifier( + minWidth = width, + maxWidth = width, + enforceIncoming = true, + inspectorInfo = + debugInspectorInfo { + name = "width" + value = width + } + ) + ) + +/** @see androidx.compose.foundation.layout.height */ +fun Modifier.height(height: Density.() -> Int) = + this.then( + SizeModifier( + minHeight = height, + maxHeight = height, + enforceIncoming = true, + inspectorInfo = + debugInspectorInfo { + name = "height" + value = height + } + ) + ) + +/** @see androidx.compose.foundation.layout.size */ +fun Modifier.size(width: Density.() -> Int, height: Density.() -> Int) = + this.then( + SizeModifier( + minWidth = width, + maxWidth = width, + minHeight = height, + maxHeight = height, + enforceIncoming = true, + inspectorInfo = + debugInspectorInfo { + name = "size" + properties["width"] = width + properties["height"] = height + } + ) + ) + +private val SizeUnspecified: Density.() -> Int = { 0 } + +private class SizeModifier( + private val minWidth: Density.() -> Int = SizeUnspecified, + private val minHeight: Density.() -> Int = SizeUnspecified, + private val maxWidth: Density.() -> Int = SizeUnspecified, + private val maxHeight: Density.() -> Int = SizeUnspecified, + private val enforceIncoming: Boolean, + inspectorInfo: InspectorInfo.() -> Unit +) : LayoutModifier, InspectorValueInfo(inspectorInfo) { + private val Density.targetConstraints: Constraints + get() { + val maxWidth = + if (maxWidth != SizeUnspecified) { + maxWidth().coerceAtLeast(0) + } else { + Constraints.Infinity + } + val maxHeight = + if (maxHeight != SizeUnspecified) { + maxHeight().coerceAtLeast(0) + } else { + Constraints.Infinity + } + val minWidth = + if (minWidth != SizeUnspecified) { + minWidth().coerceAtMost(maxWidth).coerceAtLeast(0).let { + if (it != Constraints.Infinity) it else 0 + } + } else { + 0 + } + val minHeight = + if (minHeight != SizeUnspecified) { + minHeight().coerceAtMost(maxHeight).coerceAtLeast(0).let { + if (it != Constraints.Infinity) it else 0 + } + } else { + 0 + } + return Constraints( + minWidth = minWidth, + minHeight = minHeight, + maxWidth = maxWidth, + maxHeight = maxHeight + ) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val wrappedConstraints = + targetConstraints.let { targetConstraints -> + if (enforceIncoming) { + constraints.constrain(targetConstraints) + } else { + val resolvedMinWidth = + if (minWidth != SizeUnspecified) { + targetConstraints.minWidth + } else { + constraints.minWidth.coerceAtMost(targetConstraints.maxWidth) + } + val resolvedMaxWidth = + if (maxWidth != SizeUnspecified) { + targetConstraints.maxWidth + } else { + constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth) + } + val resolvedMinHeight = + if (minHeight != SizeUnspecified) { + targetConstraints.minHeight + } else { + constraints.minHeight.coerceAtMost(targetConstraints.maxHeight) + } + val resolvedMaxHeight = + if (maxHeight != SizeUnspecified) { + targetConstraints.maxHeight + } else { + constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight) + } + Constraints( + resolvedMinWidth, + resolvedMaxWidth, + resolvedMinHeight, + resolvedMaxHeight + ) + } + } + val placeable = measurable.measure(wrappedConstraints) + return layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) } + } + + override fun IntrinsicMeasureScope.minIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ): Int { + val constraints = targetConstraints + return if (constraints.hasFixedWidth) { + constraints.maxWidth + } else { + constraints.constrainWidth(measurable.minIntrinsicWidth(height)) + } + } + + override fun IntrinsicMeasureScope.minIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ): Int { + val constraints = targetConstraints + return if (constraints.hasFixedHeight) { + constraints.maxHeight + } else { + constraints.constrainHeight(measurable.minIntrinsicHeight(width)) + } + } + + override fun IntrinsicMeasureScope.maxIntrinsicWidth( + measurable: IntrinsicMeasurable, + height: Int + ): Int { + val constraints = targetConstraints + return if (constraints.hasFixedWidth) { + constraints.maxWidth + } else { + constraints.constrainWidth(measurable.maxIntrinsicWidth(height)) + } + } + + override fun IntrinsicMeasureScope.maxIntrinsicHeight( + measurable: IntrinsicMeasurable, + width: Int + ): Int { + val constraints = targetConstraints + return if (constraints.hasFixedHeight) { + constraints.maxHeight + } else { + constraints.constrainHeight(measurable.maxIntrinsicHeight(width)) + } + } + + override fun equals(other: Any?): Boolean { + if (other !is SizeModifier) return false + return minWidth == other.minWidth && + minHeight == other.minHeight && + maxWidth == other.maxWidth && + maxHeight == other.maxHeight && + enforceIncoming == other.enforceIncoming + } + + override fun hashCode() = + (((((minWidth.hashCode() * 31 + minHeight.hashCode()) * 31) + maxWidth.hashCode()) * 31) + + maxHeight.hashCode()) * 31 +} diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp deleted file mode 100644 index 5a7a1e1807a3..000000000000 --- a/packages/SystemUI/compose/gallery/Android.bp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "SystemUIComposeGalleryLib", - manifest: "AndroidManifest.xml", - - srcs: [ - "src/**/*.kt", - ":SystemUI-tests-utils", - ], - - resource_dirs: [ - "res", - ], - - static_libs: [ - "SystemUI-core", - "SystemUIComposeCore", - "SystemUIComposeFeatures", - - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - "androidx.compose.material_material-icons-extended", - "androidx.activity_activity-compose", - "androidx.navigation_navigation-compose", - - "androidx.appcompat_appcompat", - - // TODO(b/240431193): Remove the dependencies and depend on - // SystemUI-test-utils directly. - "androidx.test.runner", - "mockito-target-extended-minus-junit4", - "testables", - "truth-prebuilt", - "androidx.test.uiautomator", - "kotlinx_coroutines_test", - ], - - libs: [ - "android.test.mock", - ], - - kotlincflags: ["-Xjvm-default=all"], -} - -android_app { - name: "SystemUIComposeGallery", - defaults: ["platform_app_defaults"], - manifest: "app/AndroidManifest.xml", - - static_libs: [ - "SystemUIComposeGalleryLib", - ], - - platform_apis: true, - system_ext_specific: true, - certificate: "platform", - privileged: true, - - optimize: { - proguard_flags_files: ["proguard-rules.pro"], - }, - - dxflags: ["--multi-dex"], -} diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml deleted file mode 100644 index 2f30651a6acf..000000000000 --- a/packages/SystemUI/compose/gallery/AndroidManifest.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.systemui.compose.gallery"> - <!-- To emulate a display size and density. --> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - - <application - android:name="android.app.Application" - android:appComponentFactory="androidx.core.app.AppComponentFactory" - tools:replace="android:name,android:appComponentFactory"> - <!-- Disable providers from SystemUI --> - <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider" - android:authorities="com.android.systemui.test.keyguard.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> - <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" - android:authorities="com.android.systemui.test.keyguard.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> - <provider android:name="com.android.keyguard.clock.ClockOptionsProvider" - android:authorities="com.android.systemui.test.keyguard.clock.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> - <provider android:name="com.android.systemui.people.PeopleProvider" - android:authorities="com.android.systemui.test.people.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> - <provider android:name="androidx.core.content.FileProvider" - android:authorities="com.android.systemui.test.fileprovider.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove"/> - </application> -</manifest> diff --git a/packages/SystemUI/compose/gallery/TEST_MAPPING b/packages/SystemUI/compose/gallery/TEST_MAPPING deleted file mode 100644 index c7f8a9216418..000000000000 --- a/packages/SystemUI/compose/gallery/TEST_MAPPING +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presubmit": [ - { - "name": "SystemUIComposeGalleryTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml deleted file mode 100644 index 1f3fd8c312d9..000000000000 --- a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.systemui.compose.gallery.app"> - <application - android:allowBackup="true" - android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" - android:supportsRtl="true" - android:theme="@style/Theme.SystemUI.Gallery" - tools:replace="android:icon,android:theme,android:label"> - <activity - android:name="com.android.systemui.compose.gallery.GalleryActivity" - android:exported="true" - android:label="@string/app_name"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/packages/SystemUI/compose/gallery/proguard-rules.pro b/packages/SystemUI/compose/gallery/proguard-rules.pro deleted file mode 100644 index 481bb4348141..000000000000 --- a/packages/SystemUI/compose/gallery/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg Binary files differdeleted file mode 100644 index 6241b0b44bb6..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg Binary files differdeleted file mode 100644 index 870ef13ee2d9..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg Binary files differdeleted file mode 100644 index bb7261c10033..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg Binary files differdeleted file mode 100644 index e34b7ddf58ce..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg Binary files differdeleted file mode 100644 index 9cde24be59ef..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg Binary files differdeleted file mode 100644 index 17825b639a26..000000000000 --- a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg +++ /dev/null diff --git a/packages/SystemUI/compose/gallery/res/values/colors.xml b/packages/SystemUI/compose/gallery/res/values/colors.xml deleted file mode 100644 index a2fcbffc26c0..000000000000 --- a/packages/SystemUI/compose/gallery/res/values/colors.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <color name="ic_launcher_background">#FFFFFF</color> -</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/strings.xml b/packages/SystemUI/compose/gallery/res/values/strings.xml deleted file mode 100644 index 86bdb0568837..000000000000 --- a/packages/SystemUI/compose/gallery/res/values/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- Application name [CHAR LIMIT=NONE] --> - <string name="app_name">SystemUI Gallery</string> -</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml deleted file mode 100644 index 45fa1f5dfb5c..000000000000 --- a/packages/SystemUI/compose/gallery/res/values/themes.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources xmlns:tools="http://schemas.android.com/tools"> - <style name="Theme.SystemUI.Gallery"> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> - - <item name="android:statusBarColor" tools:targetApi="l"> - @android:color/transparent - </item> - <item name="android:navigationBarColor" tools:targetApi="l"> - @android:color/transparent - </item> - <item name="android:windowLightStatusBar">true</item> - </style> -</resources> diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt deleted file mode 100644 index 881a1def113a..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -@file:OptIn(ExperimentalMaterial3Api::class) - -package com.android.systemui.compose.gallery - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.android.systemui.compose.SysUiButton -import com.android.systemui.compose.SysUiOutlinedButton -import com.android.systemui.compose.SysUiTextButton - -@Composable -fun ButtonsScreen( - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - ) { - SysUiButton( - onClick = {}, - ) { - Text("SysUiButton") - } - - SysUiButton( - onClick = {}, - enabled = false, - ) { - Text("SysUiButton - disabled") - } - - SysUiOutlinedButton( - onClick = {}, - ) { - Text("SysUiOutlinedButton") - } - - SysUiOutlinedButton( - onClick = {}, - enabled = false, - ) { - Text("SysUiOutlinedButton - disabled") - } - - SysUiTextButton( - onClick = {}, - ) { - Text("SysUiTextButton") - } - - SysUiTextButton( - onClick = {}, - enabled = false, - ) { - Text("SysUiTextButton - disabled") - } - } -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt deleted file mode 100644 index dfa1b26f464e..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import com.android.systemui.compose.theme.LocalAndroidColorScheme - -/** The screen that shows all the Material 3 colors. */ -@Composable -fun MaterialColorsScreen() { - val colors = MaterialTheme.colorScheme - ColorsScreen( - listOf( - "primary" to colors.primary, - "onPrimary" to colors.onPrimary, - "primaryContainer" to colors.primaryContainer, - "onPrimaryContainer" to colors.onPrimaryContainer, - "inversePrimary" to colors.inversePrimary, - "secondary" to colors.secondary, - "onSecondary" to colors.onSecondary, - "secondaryContainer" to colors.secondaryContainer, - "onSecondaryContainer" to colors.onSecondaryContainer, - "tertiary" to colors.tertiary, - "onTertiary" to colors.onTertiary, - "tertiaryContainer" to colors.tertiaryContainer, - "onTertiaryContainer" to colors.onTertiaryContainer, - "background" to colors.background, - "onBackground" to colors.onBackground, - "surface" to colors.surface, - "onSurface" to colors.onSurface, - "surfaceVariant" to colors.surfaceVariant, - "onSurfaceVariant" to colors.onSurfaceVariant, - "inverseSurface" to colors.inverseSurface, - "inverseOnSurface" to colors.inverseOnSurface, - "error" to colors.error, - "onError" to colors.onError, - "errorContainer" to colors.errorContainer, - "onErrorContainer" to colors.onErrorContainer, - "outline" to colors.outline, - ) - ) -} - -/** The screen that shows all the Android colors. */ -@Composable -fun AndroidColorsScreen() { - val colors = LocalAndroidColorScheme.current - ColorsScreen( - listOf( - "colorPrimary" to colors.colorPrimary, - "colorPrimaryDark" to colors.colorPrimaryDark, - "colorAccent" to colors.colorAccent, - "colorAccentPrimary" to colors.colorAccentPrimary, - "colorAccentSecondary" to colors.colorAccentSecondary, - "colorAccentTertiary" to colors.colorAccentTertiary, - "colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant, - "colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant, - "colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant, - "colorSurface" to colors.colorSurface, - "colorSurfaceHighlight" to colors.colorSurfaceHighlight, - "colorSurfaceVariant" to colors.colorSurfaceVariant, - "colorSurfaceHeader" to colors.colorSurfaceHeader, - "colorError" to colors.colorError, - "colorBackground" to colors.colorBackground, - "colorBackgroundFloating" to colors.colorBackgroundFloating, - "panelColorBackground" to colors.panelColorBackground, - "textColorPrimary" to colors.textColorPrimary, - "textColorSecondary" to colors.textColorSecondary, - "textColorTertiary" to colors.textColorTertiary, - "textColorPrimaryInverse" to colors.textColorPrimaryInverse, - "textColorSecondaryInverse" to colors.textColorSecondaryInverse, - "textColorTertiaryInverse" to colors.textColorTertiaryInverse, - "textColorOnAccent" to colors.textColorOnAccent, - "colorForeground" to colors.colorForeground, - "colorForegroundInverse" to colors.colorForegroundInverse, - ) - ) -} - -@Composable -private fun ColorsScreen( - colors: List<Pair<String, Color>>, -) { - LazyColumn( - Modifier.fillMaxWidth(), - ) { - colors.forEach { (name, color) -> item { ColorTile(color, name) } } - } -} - -@Composable -private fun ColorTile( - color: Color, - name: String, -) { - Row( - Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - val shape = RoundedCornerShape(16.dp) - Spacer( - Modifier.border(1.dp, MaterialTheme.colorScheme.onBackground, shape) - .background(color, shape) - .size(64.dp) - ) - Spacer(Modifier.width(16.dp)) - Text(name) - } -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt deleted file mode 100644 index 990d060207df..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt +++ /dev/null @@ -1,210 +0,0 @@ -package com.android.systemui.compose.gallery - -import android.graphics.Point -import android.os.UserHandle -import android.view.Display -import android.view.WindowManagerGlobal -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DarkMode -import androidx.compose.material.icons.filled.FormatSize -import androidx.compose.material.icons.filled.FormatTextdirectionLToR -import androidx.compose.material.icons.filled.FormatTextdirectionRToL -import androidx.compose.material.icons.filled.InvertColors -import androidx.compose.material.icons.filled.LightMode -import androidx.compose.material.icons.filled.Smartphone -import androidx.compose.material.icons.filled.Tablet -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import kotlin.math.max -import kotlin.math.min - -enum class FontScale(val scale: Float) { - Small(0.85f), - Normal(1f), - Big(1.15f), - Bigger(1.30f), -} - -/** A configuration panel that allows to toggle the theme, font scale and layout direction. */ -@Composable -fun ConfigurationControls( - theme: Theme, - fontScale: FontScale, - layoutDirection: LayoutDirection, - onChangeTheme: () -> Unit, - onChangeLayoutDirection: () -> Unit, - onChangeFontScale: () -> Unit, - modifier: Modifier = Modifier, -) { - // The display we are emulating, if any. - var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) } - val emulatedDisplay = - emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } } - - LaunchedEffect(emulatedDisplay) { - val wm = WindowManagerGlobal.getWindowManagerService() - - val defaultDisplayId = Display.DEFAULT_DISPLAY - if (emulatedDisplay == null) { - wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId()) - wm.clearForcedDisplaySize(defaultDisplayId) - } else { - val density = emulatedDisplay.densityDpi - - // Emulate the display and make sure that we use the maximum available space possible. - val initialSize = Point() - wm.getInitialDisplaySize(defaultDisplayId, initialSize) - val width = emulatedDisplay.width - val height = emulatedDisplay.height - val minOfSize = min(width, height) - val maxOfSize = max(width, height) - if (initialSize.x < initialSize.y) { - wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize) - } else { - wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize) - } - wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId()) - } - } - - // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users - // don't miss any available configuration. - LazyRow(modifier) { - // Dark/light theme. - item { - TextButton(onChangeTheme) { - val text: String - val icon: ImageVector - - when (theme) { - Theme.System -> { - icon = Icons.Default.InvertColors - text = "System" - } - Theme.Dark -> { - icon = Icons.Default.DarkMode - text = "Dark" - } - Theme.Light -> { - icon = Icons.Default.LightMode - text = "Light" - } - } - - Icon(icon, null) - Spacer(Modifier.width(8.dp)) - Text(text) - } - } - - // Font scale. - item { - TextButton(onChangeFontScale) { - Icon(Icons.Default.FormatSize, null) - Spacer(Modifier.width(8.dp)) - - Text(fontScale.name) - } - } - - // Layout direction. - item { - TextButton(onChangeLayoutDirection) { - when (layoutDirection) { - LayoutDirection.Ltr -> { - Icon(Icons.Default.FormatTextdirectionLToR, null) - Spacer(Modifier.width(8.dp)) - Text("LTR") - } - LayoutDirection.Rtl -> { - Icon(Icons.Default.FormatTextdirectionRToL, null) - Spacer(Modifier.width(8.dp)) - Text("RTL") - } - } - } - } - - // Display emulation. - EmulatedDisplays.forEach { display -> - item { - DisplayButton( - display, - emulatedDisplay == display, - { emulatedDisplayName = it?.name }, - ) - } - } - } -} - -@Composable -private fun DisplayButton( - display: EmulatedDisplay, - selected: Boolean, - onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit, -) { - val onClick = { - if (selected) { - onChangeEmulatedDisplay(null) - } else { - onChangeEmulatedDisplay(display) - } - } - - val content: @Composable RowScope.() -> Unit = { - Icon(display.icon, null) - Spacer(Modifier.width(8.dp)) - Text(display.name) - } - - if (selected) { - Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content) - } else { - TextButton(onClick, content = content) - } -} - -/** The displays that can be emulated from this Gallery app. */ -private val EmulatedDisplays = - listOf( - EmulatedDisplay( - "Phone", - Icons.Default.Smartphone, - width = 1440, - height = 3120, - densityDpi = 560, - ), - EmulatedDisplay( - "Tablet", - Icons.Default.Tablet, - width = 2560, - height = 1600, - densityDpi = 320, - ), - ) - -private data class EmulatedDisplay( - val name: String, - val icon: ImageVector, - val width: Int, - val height: Int, - val densityDpi: Int, -) diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt deleted file mode 100644 index bb2d2feba39f..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import android.app.UiModeManager -import android.content.Context -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.graphics.Color -import androidx.core.view.WindowCompat -import com.android.systemui.compose.rememberSystemUiController - -class GalleryActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) - val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager - - setContent { - var theme by rememberSaveable { mutableStateOf(Theme.System) } - val onChangeTheme = { - // Change to the next theme for a toggle behavior. - theme = - when (theme) { - Theme.System -> Theme.Dark - Theme.Dark -> Theme.Light - Theme.Light -> Theme.System - } - } - - val isSystemInDarkTheme = isSystemInDarkTheme() - val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme) - val useDarkIcons = !isDark - val systemUiController = rememberSystemUiController() - SideEffect { - systemUiController.setSystemBarsColor( - color = Color.Transparent, - darkIcons = useDarkIcons, - ) - - uiModeManager.setApplicationNightMode( - when (theme) { - Theme.System -> UiModeManager.MODE_NIGHT_AUTO - Theme.Dark -> UiModeManager.MODE_NIGHT_YES - Theme.Light -> UiModeManager.MODE_NIGHT_NO - } - ) - } - - GalleryApp(theme, onChangeTheme) - } - } -} - -enum class Theme { - System, - Dark, - Light, -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt deleted file mode 100644 index 6805bf83dff4..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.android.systemui.compose.gallery - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import com.android.systemui.compose.theme.SystemUITheme - -/** The gallery app screens. */ -object GalleryAppScreens { - private val Typography = ChildScreen("typography") { TypographyScreen() } - private val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } - private val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } - private val Buttons = ChildScreen("buttons") { ButtonsScreen() } - private val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } - - private val PeopleEmpty = - ChildScreen("people_empty") { navController -> - EmptyPeopleScreen(onResult = { navController.popBackStack() }) - } - private val PeopleFew = - ChildScreen("people_few") { navController -> - FewPeopleScreen(onResult = { navController.popBackStack() }) - } - private val PeopleFull = - ChildScreen("people_full") { navController -> - FullPeopleScreen(onResult = { navController.popBackStack() }) - } - private val People = - ParentScreen( - "people", - mapOf( - "Empty" to PeopleEmpty, - "Few" to PeopleFew, - "Full" to PeopleFull, - ) - ) - private val UserSwitcherSingleUser = - ChildScreen("user_switcher_single") { navController -> - UserSwitcherScreen( - userCount = 1, - onFinished = navController::popBackStack, - ) - } - private val UserSwitcherThreeUsers = - ChildScreen("user_switcher_three") { navController -> - UserSwitcherScreen( - userCount = 3, - onFinished = navController::popBackStack, - ) - } - private val UserSwitcherFourUsers = - ChildScreen("user_switcher_four") { navController -> - UserSwitcherScreen( - userCount = 4, - onFinished = navController::popBackStack, - ) - } - private val UserSwitcherFiveUsers = - ChildScreen("user_switcher_five") { navController -> - UserSwitcherScreen( - userCount = 5, - onFinished = navController::popBackStack, - ) - } - private val UserSwitcherSixUsers = - ChildScreen("user_switcher_six") { navController -> - UserSwitcherScreen( - userCount = 6, - onFinished = navController::popBackStack, - ) - } - private val UserSwitcher = - ParentScreen( - "user_switcher", - mapOf( - "Single" to UserSwitcherSingleUser, - "Three" to UserSwitcherThreeUsers, - "Four" to UserSwitcherFourUsers, - "Five" to UserSwitcherFiveUsers, - "Six" to UserSwitcherSixUsers, - ) - ) - - val Home = - ParentScreen( - "home", - mapOf( - "Typography" to Typography, - "Material colors" to MaterialColors, - "Android colors" to AndroidColors, - "Example feature" to ExampleFeature, - "Buttons" to Buttons, - "People" to People, - "User Switcher" to UserSwitcher, - ) - ) -} - -/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */ -@Composable -private fun MainContent(onControlToggleRequested: () -> Unit) { - Box(Modifier.fillMaxSize()) { - val navController = rememberNavController() - NavHost( - navController = navController, - startDestination = GalleryAppScreens.Home.identifier, - ) { - screen(GalleryAppScreens.Home, navController, onControlToggleRequested) - } - } -} - -/** - * The top-level composable shown when starting the app. This composable always shows a - * [ConfigurationControls] at the top of the screen, above the [MainContent]. - */ -@Composable -fun GalleryApp( - theme: Theme, - onChangeTheme: () -> Unit, -) { - val systemFontScale = LocalDensity.current.fontScale - var fontScale: FontScale by rememberSaveable { - mutableStateOf( - FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal - ) - } - val context = LocalContext.current - val density = Density(context.resources.displayMetrics.density, fontScale.scale) - val onChangeFontScale = { - fontScale = - when (fontScale) { - FontScale.Small -> FontScale.Normal - FontScale.Normal -> FontScale.Big - FontScale.Big -> FontScale.Bigger - FontScale.Bigger -> FontScale.Small - } - } - - val systemLayoutDirection = LocalLayoutDirection.current - var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) } - val onChangeLayoutDirection = { - layoutDirection = - when (layoutDirection) { - LayoutDirection.Ltr -> LayoutDirection.Rtl - LayoutDirection.Rtl -> LayoutDirection.Ltr - } - } - - CompositionLocalProvider( - LocalDensity provides density, - LocalLayoutDirection provides layoutDirection, - ) { - SystemUITheme { - Surface( - Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, - ) { - Column(Modifier.fillMaxSize().systemBarsPadding()) { - var showControls by rememberSaveable { mutableStateOf(true) } - - if (showControls) { - ConfigurationControls( - theme, - fontScale, - layoutDirection, - onChangeTheme, - onChangeLayoutDirection, - onChangeFontScale, - Modifier.padding(horizontal = 16.dp), - ) - - Spacer(Modifier.height(4.dp)) - } - - MainContent(onControlToggleRequested = { showControls = !showControls }) - } - } - } - } -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt deleted file mode 100644 index 2f0df7790ffd..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import com.android.systemui.people.emptyPeopleSpaceViewModel -import com.android.systemui.people.fewPeopleSpaceViewModel -import com.android.systemui.people.fullPeopleSpaceViewModel -import com.android.systemui.people.ui.compose.PeopleScreen -import com.android.systemui.people.ui.viewmodel.PeopleViewModel - -@Composable -fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { - val context = LocalContext.current.applicationContext - val viewModel = emptyPeopleSpaceViewModel(context) - PeopleScreen(viewModel, onResult) -} - -@Composable -fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { - val context = LocalContext.current.applicationContext - val viewModel = fewPeopleSpaceViewModel(context) - PeopleScreen(viewModel, onResult) -} - -@Composable -fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { - val context = LocalContext.current.applicationContext - val viewModel = fullPeopleSpaceViewModel(context) - PeopleScreen(viewModel, onResult) -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt deleted file mode 100644 index d7d0d721b01c..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import androidx.navigation.compose.navigation - -/** - * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to - * them or a [ChildScreen] which shows some content. - */ -sealed class Screen(val identifier: String) - -class ParentScreen( - identifier: String, - val children: Map<String, Screen>, -) : Screen(identifier) - -class ChildScreen( - identifier: String, - val content: @Composable (NavController) -> Unit, -) : Screen(identifier) - -/** Create the navigation graph for [screen]. */ -fun NavGraphBuilder.screen( - screen: Screen, - navController: NavController, - onControlToggleRequested: () -> Unit, -) { - when (screen) { - is ChildScreen -> composable(screen.identifier) { screen.content(navController) } - is ParentScreen -> { - val menuRoute = "${screen.identifier}_menu" - navigation(startDestination = menuRoute, route = screen.identifier) { - // The menu to navigate to one of the children screens. - composable(menuRoute) { - ScreenMenu(screen, navController, onControlToggleRequested) - } - - // The content of the child screens. - screen.children.forEach { (_, child) -> - screen( - child, - navController, - onControlToggleRequested, - ) - } - } - } - } -} - -@Composable -private fun ScreenMenu( - screen: ParentScreen, - navController: NavController, - onControlToggleRequested: () -> Unit, -) { - LazyColumn( - Modifier.padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - item { - Surface( - Modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.tertiaryContainer, - shape = CircleShape, - ) { - Column( - Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text("Toggle controls") - } - } - } - - screen.children.forEach { (name, child) -> - item { - Surface( - Modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.secondaryContainer, - shape = CircleShape, - ) { - Column( - Modifier.clickable { navController.navigate(child.identifier) } - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text(name) - } - } - } - } - } -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt deleted file mode 100644 index 147025ed1d60..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextOverflow - -/** The screen that shows the Material text styles. */ -@Composable -fun TypographyScreen() { - val typography = MaterialTheme.typography - - Column( - Modifier.fillMaxSize() - .horizontalScroll(rememberScrollState()) - .verticalScroll(rememberScrollState()), - ) { - FontLine("displayLarge", typography.displayLarge) - FontLine("displayMedium", typography.displayMedium) - FontLine("displaySmall", typography.displaySmall) - FontLine("headlineLarge", typography.headlineLarge) - FontLine("headlineMedium", typography.headlineMedium) - FontLine("headlineSmall", typography.headlineSmall) - FontLine("titleLarge", typography.titleLarge) - FontLine("titleMedium", typography.titleMedium) - FontLine("titleSmall", typography.titleSmall) - FontLine("bodyLarge", typography.bodyLarge) - FontLine("bodyMedium", typography.bodyMedium) - FontLine("bodySmall", typography.bodySmall) - FontLine("labelLarge", typography.labelLarge) - FontLine("labelMedium", typography.labelMedium) - FontLine("labelSmall", typography.labelSmall) - } -} - -@Composable -private fun FontLine(name: String, style: TextStyle) { - Text( - "$name (${style.fontSize}/${style.lineHeight}, W${style.fontWeight?.weight})", - style = style, - maxLines = 1, - overflow = TextOverflow.Visible, - ) -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt deleted file mode 100644 index fe9707d22684..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import com.android.systemui.user.Fakes.fakeUserSwitcherViewModel -import com.android.systemui.user.ui.compose.UserSwitcherScreen - -@Composable -fun UserSwitcherScreen( - userCount: Int, - onFinished: () -> Unit, -) { - val context = LocalContext.current.applicationContext - UserSwitcherScreen( - viewModel = fakeUserSwitcherViewModel(context, userCount = userCount), - onFinished = onFinished, - ) -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt deleted file mode 100644 index 0966c3233ad5..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.people - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.drawable.Icon -import androidx.core.graphics.drawable.toIcon -import com.android.systemui.R -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.people.data.model.PeopleTileModel -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.people.widget.PeopleTileKey - -/** A [PeopleViewModel] that does not have any conversations. */ -fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { - return fakePeopleSpaceViewModel(context, emptyList(), emptyList()) -} - -/** A [PeopleViewModel] that has a few conversations. */ -fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { - return fakePeopleSpaceViewModel( - context, - priorityTiles = - listOf( - fakeTile(context, id = "0", Color.RED, "Priority"), - fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true), - ), - recentTiles = - listOf( - fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true), - fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true), - ), - ) -} - -/** A [PeopleViewModel] that has a lot of conversations. */ -fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { - return fakePeopleSpaceViewModel( - context, - priorityTiles = - listOf( - fakeTile(context, id = "0", Color.RED, "Priority"), - fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true), - fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true), - fakeTile( - context, - id = "3", - Color.CYAN, - "Priority DndBlocking", - isDndBlocking = true, - ), - fakeTile( - context, - id = "4", - Color.MAGENTA, - "Priority NewStory Important", - hasNewStory = true, - isImportant = true, - ), - ), - recentTiles = - listOf( - fakeTile( - context, - id = "5", - Color.RED, - "Recent NewStory DndBlocking", - hasNewStory = true, - isDndBlocking = true, - ), - fakeTile( - context, - id = "6", - Color.BLUE, - "Recent Important DndBlocking", - isImportant = true, - isDndBlocking = true, - ), - fakeTile( - context, - id = "7", - Color.GREEN, - "Recent NewStory Important DndBlocking", - hasNewStory = true, - isImportant = true, - isDndBlocking = true, - ), - fakeTile(context, id = "8", Color.CYAN, "Recent"), - fakeTile(context, id = "9", Color.MAGENTA, "Recent"), - ), - ) -} - -private fun fakePeopleSpaceViewModel( - @Application context: Context, - priorityTiles: List<PeopleTileModel>, - recentTiles: List<PeopleTileModel>, -): PeopleViewModel { - return PeopleViewModel( - context, - FakePeopleTileRepository(priorityTiles, recentTiles), - FakePeopleWidgetRepository(), - ) -} - -private fun fakeTile( - @Application context: Context, - id: String, - iconColor: Int, - username: String, - hasNewStory: Boolean = false, - isImportant: Boolean = false, - isDndBlocking: Boolean = false -): PeopleTileModel { - return PeopleTileModel( - PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""), - username, - fakeUserIcon(context, iconColor), - hasNewStory, - isImportant, - isDndBlocking, - ) -} - -private fun fakeUserIcon(@Application context: Context, color: Int): Icon { - val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium) - val bitmap = - Bitmap.createBitmap( - size, - size, - Bitmap.Config.ARGB_8888, - ) - val canvas = Canvas(bitmap) - val paint = Paint().apply { this.color = color } - val radius = size / 2f - canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint) - return bitmap.toIcon() -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt deleted file mode 100644 index 6588e22721fb..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.footer - -import android.content.Context -import android.os.UserHandle -import android.view.View -import com.android.internal.util.UserIcons -import com.android.systemui.R -import com.android.systemui.animation.Expandable -import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.globalactions.GlobalActionsDialogLite -import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel -import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor -import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -/** A list of fake [FooterActionsViewModel] to be used in screenshot tests and the gallery. */ -fun fakeFooterActionsViewModels( - @Application context: Context, -): List<FooterActionsViewModel> { - return listOf( - fakeFooterActionsViewModel(context), - fakeFooterActionsViewModel(context, showPowerButton = false, isGuestUser = true), - fakeFooterActionsViewModel(context, showUserSwitcher = false), - fakeFooterActionsViewModel(context, showUserSwitcher = false, foregroundServices = 4), - fakeFooterActionsViewModel( - context, - foregroundServices = 4, - hasNewForegroundServices = true, - userId = 1, - ), - fakeFooterActionsViewModel( - context, - securityText = "Security", - foregroundServices = 4, - showUserSwitcher = false, - ), - fakeFooterActionsViewModel( - context, - securityText = "Security (not clickable)", - securityClickable = false, - foregroundServices = 4, - hasNewForegroundServices = true, - userId = 2, - ), - ) -} - -private fun fakeFooterActionsViewModel( - @Application context: Context, - securityText: String? = null, - securityClickable: Boolean = true, - foregroundServices: Int = 0, - hasNewForegroundServices: Boolean = false, - showUserSwitcher: Boolean = true, - showPowerButton: Boolean = true, - userId: Int = UserHandle.USER_OWNER, - isGuestUser: Boolean = false, -): FooterActionsViewModel { - val interactor = - FakeFooterActionsInteractor( - securityButtonConfig = - flowOf( - securityText?.let { text -> - SecurityButtonConfig( - icon = - Icon.Resource( - R.drawable.ic_info_outline, - contentDescription = null, - ), - text = text, - isClickable = securityClickable, - ) - } - ), - foregroundServicesCount = flowOf(foregroundServices), - hasNewForegroundServices = flowOf(hasNewForegroundServices), - userSwitcherStatus = - flowOf( - if (showUserSwitcher) { - UserSwitcherStatusModel.Enabled( - currentUserName = "foo", - currentUserImage = - UserIcons.getDefaultUserIcon( - context.resources, - userId, - /* light= */ false, - ), - isGuestUser = isGuestUser, - ) - } else { - UserSwitcherStatusModel.Disabled - } - ), - deviceMonitoringDialogRequests = flowOf(), - ) - - return FooterActionsViewModel( - context, - interactor, - FalsingManagerFake(), - globalActionsDialogLite = mock(), - showPowerButton = showPowerButton, - ) -} - -private class FakeFooterActionsInteractor( - override val securityButtonConfig: Flow<SecurityButtonConfig?> = flowOf(null), - override val foregroundServicesCount: Flow<Int> = flowOf(0), - override val hasNewForegroundServices: Flow<Boolean> = flowOf(false), - override val userSwitcherStatus: Flow<UserSwitcherStatusModel> = - flowOf(UserSwitcherStatusModel.Disabled), - override val deviceMonitoringDialogRequests: Flow<Unit> = flowOf(), - private val onShowDeviceMonitoringDialogFromView: (View) -> Unit = {}, - private val onShowDeviceMonitoringDialog: (Context) -> Unit = {}, - private val onShowForegroundServicesDialog: (View) -> Unit = {}, - private val onShowPowerMenuDialog: (GlobalActionsDialogLite, View) -> Unit = { _, _ -> }, - private val onShowSettings: (Expandable) -> Unit = {}, - private val onShowUserSwitcher: (View) -> Unit = {}, -) : FooterActionsInteractor { - override fun showDeviceMonitoringDialog(view: View) { - onShowDeviceMonitoringDialogFromView(view) - } - - override fun showDeviceMonitoringDialog(quickSettingsContext: Context) { - onShowDeviceMonitoringDialog(quickSettingsContext) - } - - override fun showForegroundServicesDialog(view: View) { - onShowForegroundServicesDialog(view) - } - - override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) { - onShowPowerMenuDialog(globalActionsDialogLite, view) - } - - override fun showSettings(expandable: Expandable) { - onShowSettings(expandable) - } - - override fun showUserSwitcher(view: View) { - onShowUserSwitcher(view) - } -} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt deleted file mode 100644 index 91a73ea16dc4..000000000000 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.user - -import android.content.Context -import androidx.appcompat.content.res.AppCompatResources -import com.android.systemui.common.shared.model.Text -import com.android.systemui.compose.gallery.R -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.domain.interactor.UserInteractor -import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.user.shared.model.UserModel -import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.util.mockito.mock - -object Fakes { - private val USER_TINT_COLORS = - arrayOf( - 0x000000, - 0x0000ff, - 0x00ff00, - 0x00ffff, - 0xff0000, - 0xff00ff, - 0xffff00, - 0xffffff, - ) - - fun fakeUserSwitcherViewModel( - context: Context, - userCount: Int, - ): UserSwitcherViewModel { - return UserSwitcherViewModel.Factory( - userInteractor = - UserInteractor( - repository = - FakeUserRepository().apply { - setUsers( - (0 until userCount).map { index -> - UserModel( - id = index, - name = - Text.Loaded( - when (index % 6) { - 0 -> "Ross Geller" - 1 -> "Phoebe Buffay" - 2 -> "Monica Geller" - 3 -> "Rachel Greene" - 4 -> "Chandler Bing" - else -> "Joey Tribbiani" - } - ), - image = - checkNotNull( - AppCompatResources.getDrawable( - context, - when (index % 6) { - 0 -> R.drawable.kitten1 - 1 -> R.drawable.kitten2 - 2 -> R.drawable.kitten3 - 3 -> R.drawable.kitten4 - 4 -> R.drawable.kitten5 - else -> R.drawable.kitten6 - }, - ) - ), - isSelected = index == 0, - isSelectable = true, - ) - } - ) - setActions( - UserActionModel.values().mapNotNull { - if (it == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) { - null - } else { - it - } - } - ) - }, - controller = mock(), - activityStarter = mock(), - keyguardInteractor = - KeyguardInteractor( - repository = - FakeKeyguardRepository().apply { setKeyguardShowing(false) }, - ), - ), - powerInteractor = - PowerInteractor( - repository = FakePowerRepository(), - ) - ) - .create(UserSwitcherViewModel::class.java) - } -} diff --git a/packages/SystemUI/compose/gallery/tests/Android.bp b/packages/SystemUI/compose/gallery/tests/Android.bp deleted file mode 100644 index 3e01f7d2c431..000000000000 --- a/packages/SystemUI/compose/gallery/tests/Android.bp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_test { - name: "SystemUIComposeGalleryTests", - manifest: "AndroidManifest.xml", - test_suites: ["device-tests"], - sdk_version: "current", - certificate: "platform", - - srcs: [ - "src/**/*.kt", - ], - - static_libs: [ - "SystemUIComposeGalleryLib", - - "androidx.test.runner", - "androidx.test.ext.junit", - - "androidx.compose.runtime_runtime", - "androidx.compose.ui_ui-test-junit4", - "androidx.compose.ui_ui-test-manifest", - ], - - kotlincflags: ["-Xjvm-default=enable"], -} diff --git a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml deleted file mode 100644 index 5eeb3ad24e5a..000000000000 --- a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.compose.gallery.tests" > - - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.systemui.compose.gallery.tests" - android:label="Tests for SystemUIComposeGallery"/> - -</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt deleted file mode 100644 index 66ecc8d4fde5..000000000000 --- a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.compose.gallery - -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.systemui.compose.theme.SystemUITheme -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ScreenshotsTests { - @get:Rule val composeRule = createComposeRule() - - @Test - fun exampleFeatureScreenshotTest() { - // TODO(b/230832101): Wire this with the screenshot diff testing infra. We should reuse the - // configuration of the features in the gallery app to populate the UIs. - composeRule.setContent { SystemUITheme { ExampleFeatureScreen() } } - } -} diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 7839ec832e2c..8307fbc4dca1 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -813,7 +813,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -828,7 +828,7 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml index ae8680a096b4..cad7159489a9 100644 --- a/packages/SystemUI/res-keyguard/values-ro/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml @@ -37,7 +37,7 @@ <string name="keyguard_missing_sim_instructions" msgid="1162120926141335918">"Introdu un card SIM."</string> <string name="keyguard_missing_sim_instructions_long" msgid="2712623293749378570">"Cardul SIM lipsește sau nu poate fi citit. Introdu un card SIM."</string> <string name="keyguard_permanent_disabled_sim_message_short" msgid="5842745213110966962">"Card SIM inutilizabil."</string> - <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"Cardul dvs. SIM este dezactivat definitiv.\n Contactați furnizorul de servicii wireless pentru a obține un alt card SIM."</string> + <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"Cardul SIM e dezactivat definitiv.\n Contactează furnizorul de servicii wireless pentru a obține un alt card SIM."</string> <string name="keyguard_sim_locked_message" msgid="4343544458476911044">"Cardul SIM este blocat."</string> <string name="keyguard_sim_puk_locked_message" msgid="6253830777745450550">"Cardul SIM este blocat cu codul PUK."</string> <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"Se deblochează cardul SIM…"</string> @@ -46,7 +46,7 @@ <string name="keyguard_accessibility_sim_pin_area" msgid="6272116591533888062">"Zona codului PIN pentru cardul SIM"</string> <string name="keyguard_accessibility_sim_puk_area" msgid="5537294043180237374">"Zona codului PUK pentru cardul SIM"</string> <string name="keyboardview_keycode_delete" msgid="8489719929424895174">"Șterge"</string> - <string name="disable_carrier_button_text" msgid="7153361131709275746">"Dezactivați cardul eSIM"</string> + <string name="disable_carrier_button_text" msgid="7153361131709275746">"Dezactivează cardul eSIM"</string> <string name="error_disable_esim_title" msgid="3802652622784813119">"Nu se poate dezactiva cardul eSIM"</string> <string name="error_disable_esim_msg" msgid="2441188596467999327">"Cardul eSIM nu poate fi dezactivat din cauza unei erori."</string> <string name="keyboardview_keycode_enter" msgid="6727192265631761174">"Introdu"</string> @@ -56,24 +56,24 @@ <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Reîncearcă peste o secundă.}few{Reîncearcă peste # secunde.}other{Reîncearcă peste # de secunde.}}"</string> <string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Introdu codul PIN al cardului SIM."</string> <string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Introdu codul PIN al cardului SIM pentru „<xliff:g id="CARRIER">%1$s</xliff:g>”."</string> - <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Dezactivați cardul eSIM pentru a folosi dispozitivul fără serviciu mobil."</string> - <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"Cardul SIM este acum dezactivat. Pentru a continua, introduceți codul PUK. Pentru detalii, contactați operatorul."</string> - <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"Cardul SIM „<xliff:g id="CARRIER">%1$s</xliff:g>\" este acum dezactivat. Pentru a continua, introduceți codul PUK. Pentru detalii, contactați operatorul."</string> + <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Dezactivează cardul eSIM pentru a folosi dispozitivul fără serviciu mobil."</string> + <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"Cardul SIM e acum dezactivat. Pentru a continua, introdu codul PUK. Pentru detalii, contactează operatorul."</string> + <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"Cardul SIM „<xliff:g id="CARRIER">%1$s</xliff:g>\" e acum dezactivat. Pentru a continua, introdu codul PUK. Pentru detalii, contactează operatorul."</string> <string name="kg_puk_enter_pin_hint" msgid="6028432138916150399">"Introdu codul PIN dorit"</string> - <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Confirmați codul PIN dorit"</string> + <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Confirmă codul PIN dorit"</string> <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"Se deblochează cardul SIM…"</string> <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"Introdu un cod PIN alcătuit din 4 până la 8 cifre."</string> <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"Codul PUK trebuie să aibă minimum 8 cifre."</string> - <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> - <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Codul PIN pentru cardul SIM este incorect. Contactați operatorul pentru a vă debloca dispozitivul."</string> + <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"Ai introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> + <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"Ai introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> + <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string> + <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Codul PIN pentru cardul SIM este incorect. Contactează operatorul pentru a debloca dispozitivul."</string> <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Codul PIN pentru cardul SIM este incorect. V-a mai rămas # încercare, după care va trebui să contactați operatorul pentru a vă debloca dispozitivul.}few{Codul PIN pentru cardul SIM este incorect. V-au mai rămas # încercări. }other{Codul PIN pentru cardul SIM este incorect. V-au mai rămas # de încercări. }}"</string> - <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"Cardul SIM nu poate fi utilizat. Contactați operatorul."</string> + <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"Cardul SIM nu poate fi utilizat. Contactează operatorul."</string> <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Codul PUK pentru cardul SIM este incorect. V-a mai rămas # încercare până când cardul SIM va deveni inutilizabil definitiv.}few{Codul PUK pentru cardul SIM este incorect. V-au mai rămas # încercări până când cardul SIM va deveni inutilizabil definitiv.}other{Codul PUK pentru cardul SIM este incorect. V-au mai rămas # de încercări până când cardul SIM va deveni inutilizabil definitiv.}}"</string> <string name="kg_password_pin_failed" msgid="5136259126330604009">"Deblocarea cu ajutorul codului PIN pentru cardul SIM nu a reușit!"</string> <string name="kg_password_puk_failed" msgid="6778867411556937118">"Deblocarea cu ajutorul codului PUK pentru cardul SIM nu a reușit!"</string> - <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Comutați metoda de introducere"</string> + <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Schimbă metoda de introducere"</string> <string name="airplane_mode" msgid="2528005343938497866">"Mod Avion"</string> <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string> diff --git a/packages/SystemUI/res-product/values-ro/strings.xml b/packages/SystemUI/res-product/values-ro/strings.xml index 54dc73ae727c..807ebfe03bef 100644 --- a/packages/SystemUI/res-product/values-ro/strings.xml +++ b/packages/SystemUI/res-product/values-ro/strings.xml @@ -21,28 +21,28 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"Repoziționați telefonul pentru încărcare mai rapidă"</string> <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"Repoziționați telefonul pentru încărcarea wireless"</string> - <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Dispozitivul Android TV se va opri în curând. Apăsați un buton pentru a-l menține pornit."</string> - <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Dispozitivul se va opri în curând. Apăsați pentru a-l menține pornit."</string> + <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Dispozitivul Android TV se va opri în curând. Apasă un buton pentru a-l menține pornit."</string> + <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Dispozitivul se va opri în curând. Apasă pentru a-l menține pornit."</string> <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Nu există card SIM în tabletă."</string> <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Nu există card SIM în telefon."</string> <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"Codurile PIN nu coincid"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, această tabletă va fi resetată, iar toate datele acesteia vor fi șterse."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string> <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Această tabletă va fi resetată, iar toate datele acesteia vor fi șterse."</string> - <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> - <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> - <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> + <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> + <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> + <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string> <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> - <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Deblocați telefonul pentru mai multe opțiuni"</string> - <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Deblocați tableta pentru mai multe opțiuni"</string> - <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Deblocați dispozitivul pentru mai multe opțiuni"</string> + <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Deblochează telefonul pentru mai multe opțiuni"</string> + <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Deblochează tableta pentru mai multe opțiuni"</string> + <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Deblochează dispozitivul pentru mai multe opțiuni"</string> <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Se redă pe acest telefon"</string> <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Se redă pe această tabletă"</string> </resources> diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml index 420ae7044149..e015ed3d0c09 100644 --- a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml +++ b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml @@ -32,23 +32,6 @@ </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_0_P_1"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="fillColor" - android:startOffset="0" - android:valueFrom="#000000" - android:valueTo="#ffffff" - android:valueType="colorType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> <target android:name="time_group"> <aapt:attr name="android:animation"> <set android:ordering="together"> @@ -72,29 +55,13 @@ <group android:name="_R_G_L_0_G" android:translateX="12" - android:translateY="12.469"> - <group - android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" - android:scaleX="1" - android:scaleY="1"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#000000" - android:fillType="nonZero" - android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " /> - </group> - <group - android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" - android:scaleX="1" - android:scaleY="1"> - <path - android:name="_R_G_L_0_G_D_0_P_1" - android:fillAlpha="1" - android:fillColor="#000000" - android:fillType="nonZero" - android:pathData=" M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " /> - </group> + android:translateY="11.969"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " /> </group> </group> <group android:name="time_group" /> diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml index 57d7902f59ca..a44a9b6fbf23 100644 --- a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml +++ b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml @@ -58,170 +58,75 @@ </aapt:attr> </objectAnimator> <objectAnimator - android:duration="767" + android:duration="67" android:propertyName="scaleX" android:startOffset="383" android:valueFrom="1.1500000000000001" - android:valueTo="1" + android:valueTo="1.1500000000000001" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" /> </aapt:attr> </objectAnimator> <objectAnimator - android:duration="767" + android:duration="67" android:propertyName="scaleY" android:startOffset="383" android:valueFrom="1.1500000000000001" - android:valueTo="1" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G_D_0_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="200" - android:propertyName="pathData" - android:startOffset="0" - android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " - android:valueTo="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c " - android:valueType="pathType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="217" - android:propertyName="pathData" - android:startOffset="200" - android:valueFrom="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c " - android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " - android:valueType="pathType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="217" - android:propertyName="pathData" - android:startOffset="417" - android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " - android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " - android:valueType="pathType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G_D_0_P_1"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="250" - android:propertyName="fillColor" - android:startOffset="0" - android:valueFrom="#ffffff" - android:valueTo="#000000" - android:valueType="colorType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="383" - android:propertyName="scaleX" - android:startOffset="0" - android:valueFrom="1" android:valueTo="1.1500000000000001" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" /> </aapt:attr> </objectAnimator> <objectAnimator - android:duration="383" - android:propertyName="scaleY" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1.1500000000000001" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="767" + android:duration="700" android:propertyName="scaleX" - android:startOffset="383" + android:startOffset="450" android:valueFrom="1.1500000000000001" android:valueTo="1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" /> </aapt:attr> </objectAnimator> <objectAnimator - android:duration="767" + android:duration="700" android:propertyName="scaleY" - android:startOffset="383" + android:startOffset="450" android:valueFrom="1.1500000000000001" android:valueTo="1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" /> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_0_P_1"> + <target android:name="_R_G_L_0_G_D_0_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> <objectAnimator android:duration="200" android:propertyName="pathData" android:startOffset="0" - android:valueFrom="M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " - android:valueTo="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c " + android:valueFrom="M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " + android:valueTo="M-3.23 8.38 C-3.23,8.38 -3.23,9.4 -3.23,9.4 C-3.23,9.4 0,8.51 0,8.51 C0,8.51 3.23,9.4 3.23,9.4 C3.23,9.4 3.23,8.38 3.23,8.38 C3.23,8.38 1.26,6.51 1.26,6.51 C1.26,6.51 1.59,1.5 1.59,1.5 C1.59,1.5 10,3.66 10,3.66 C10,3.66 10,1.95 10,1.95 C10,1.95 1.59,-3.36 1.59,-3.36 C1.59,-3.36 1.77,-8.02 1.77,-8.02 C1.77,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.77,-8.44 -1.77,-8.02 C-1.77,-8.02 -1.59,-3.36 -1.59,-3.36 C-1.59,-3.36 -10,1.95 -10,1.95 C-10,1.95 -10,3.66 -10,3.66 C-10,3.66 -1.59,1.5 -1.59,1.5 C-1.59,1.5 -1.24,6.51 -1.24,6.51 C-1.24,6.51 -3.23,8.38 -3.23,8.38c " android:valueType="pathType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> </aapt:attr> </objectAnimator> <objectAnimator - android:duration="217" + android:duration="383" android:propertyName="pathData" android:startOffset="200" - android:valueFrom="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c " - android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " + android:valueFrom="M-3.23 8.38 C-3.23,8.38 -3.23,9.4 -3.23,9.4 C-3.23,9.4 0,8.51 0,8.51 C0,8.51 3.23,9.4 3.23,9.4 C3.23,9.4 3.23,8.38 3.23,8.38 C3.23,8.38 1.26,6.51 1.26,6.51 C1.26,6.51 1.59,1.5 1.59,1.5 C1.59,1.5 10,3.66 10,3.66 C10,3.66 10,1.95 10,1.95 C10,1.95 1.59,-3.36 1.59,-3.36 C1.59,-3.36 1.77,-8.02 1.77,-8.02 C1.77,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.77,-8.44 -1.77,-8.02 C-1.77,-8.02 -1.59,-3.36 -1.59,-3.36 C-1.59,-3.36 -10,1.95 -10,1.95 C-10,1.95 -10,3.66 -10,3.66 C-10,3.66 -1.59,1.5 -1.59,1.5 C-1.59,1.5 -1.24,6.51 -1.24,6.51 C-1.24,6.51 -3.23,8.38 -3.23,8.38c " + android:valueTo="M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " android:valueType="pathType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="217" - android:propertyName="pathData" - android:startOffset="417" - android:valueFrom="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " - android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " - android:valueType="pathType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" /> </aapt:attr> </objectAnimator> </set> @@ -250,7 +155,7 @@ <group android:name="_R_G_L_0_G" android:translateX="12" - android:translateY="12.469"> + android:translateY="11.969"> <group android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" android:scaleX="1" @@ -260,18 +165,7 @@ android:fillAlpha="1" android:fillColor="#ffffff" android:fillType="nonZero" - android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " /> - </group> - <group - android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" - android:scaleX="1" - android:scaleY="1"> - <path - android:name="_R_G_L_0_G_D_0_P_1" - android:fillAlpha="1" - android:fillColor="#ffffff" - android:fillType="nonZero" - android:pathData=" M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " /> + android:pathData=" M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " /> </group> </group> </group> diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml new file mode 100644 index 000000000000..eb160de7f249 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml new file mode 100644 index 000000000000..de972a62d7f7 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="167" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml new file mode 100644 index 000000000000..e33b264ad528 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="250" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="167" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="417" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="850" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index acb47f7a037c..2d67d95ab17e 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -14,8 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.dreams.complication.DoubleShadowTextClock +<com.android.systemui.shared.shadow.DoubleShadowTextClock xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/time_view" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -25,4 +26,13 @@ android:format24Hour="@string/dream_time_complication_24_hr_time_format" android:fontFeatureSettings="pnum, lnum" android:letterSpacing="0.02" - android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/> + android:textSize="@dimen/dream_overlay_complication_clock_time_text_size" + app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius" + app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx" + app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy" + app:keyShadowAlpha="0.3" + app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius" + app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx" + app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" + app:ambientShadowAlpha="0.3" +/> diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml index 4ad6849e9c45..226bc6afc5ab 100644 --- a/packages/SystemUI/res/layout/media_projection_app_selector.xml +++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml @@ -36,13 +36,14 @@ android:background="@*android:drawable/bottomsheet_background"> <ImageView - android:id="@*android:id/icon" android:layout_width="@dimen/media_projection_app_selector_icon_size" android:layout_height="@dimen/media_projection_app_selector_icon_size" android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal" android:layout_marginBottom="@*android:dimen/chooser_edge_margin_normal" android:importantForAccessibility="no" - android:tint="?android:attr/textColorPrimary"/> + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_present_to_all" + /> <TextView android:id="@*android:id/title" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml new file mode 100644 index 000000000000..a2b3c404f077 --- /dev/null +++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical" + android:background="?android:attr/colorBackground" + > + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="256dp"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/media_projection_recent_tasks_recycler" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + /> + + <ProgressBar + android:id="@+id/media_projection_recent_tasks_loader" + android:layout_width="@dimen/media_projection_app_selector_loader_size" + android:layout_height="@dimen/media_projection_app_selector_loader_size" + android:layout_gravity="center" + android:indeterminate="true" + android:indeterminateOnly="true" /> + </FrameLayout> + + <!-- Divider line --> + <ImageView + android:layout_width="72dp" + android:layout_height="2dp" + android:layout_marginBottom="8dp" + android:layout_marginTop="24dp" + android:importantForAccessibility="no" + android:src="@*android:drawable/ic_drag_handle" + android:tint="?android:attr/textColorSecondary" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml new file mode 100644 index 000000000000..75f73cd7a1e9 --- /dev/null +++ b/packages/SystemUI/res/layout/media_projection_task_item.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:orientation="vertical"> + + <ImageView + android:id="@+id/task_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_margin="8dp" + android:importantForAccessibility="no" /> + + <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly --> + <!-- TODO(b/240924926) dynamically change the view size based on the screen size --> + <ImageView + android:id="@+id/task_thumbnail" + android:layout_width="100dp" + android:layout_height="216dp" + android:scaleType="centerCrop" + /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 0c57b934d27e..8388b67c1fa1 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -103,6 +103,7 @@ android:layout_width="match_parent" android:layout_weight="1" android:background="@android:color/transparent" + android:visibility="invisible" android:clipChildren="false" android:clipToPadding="false" /> </LinearLayout> diff --git a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json index b1616094ade4..09ed22560885 100644 --- a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json +++ b/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json @@ -1 +1 @@ -{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_topleft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey905","cl":"grey905","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey904","cl":"grey904","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey903","cl":"grey903","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey902","cl":"grey902","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey901","cl":"grey901","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file +{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 7f6f006bb993..c7731775c296 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Sommige kenmerke is beperk terwyl foon afkoel.\nTik vir meer inligting"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Jou foon sal outomaties probeer om af te koel. Jy kan steeds jou foon gebruik, maar dit sal dalk stadiger wees.\n\nJou foon sal normaalweg werk nadat dit afgekoel het."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sien versorgingstappe"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Prop jou toestel uit"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Jou toestel word tans warm naby die laaipoort. Prop dit uit as dit aan ’n laaier of USB-bykomstigheid gekoppel is. Wees versigtig, aangesien die kabel dalk ook warm is."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Sien versorgingstappe"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Links-kortpad"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Regs-kortpad"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index a3ebb60490ac..3df515b21e26 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"በርቷል"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ጠፍቷል"</string> <string name="tile_unavailable" msgid="3095879009136616920">"አይገኝም"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"የበለጠ ለመረዳት"</string> <string name="nav_bar" msgid="4642708685386136807">"የአሰሳ አሞሌ"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"አቀማመጥ"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ተጨማሪ የግራ አዝራር ዓይነት"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"አንዳንድ ባሕሪያት ስልኩ እየቀዘቀዘ እያለ ውስን ይሆናሉ።\nለተጨማሪ መረጃ መታ ያድርጉ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"የእርስዎ ስልክ በራስ-ሰር ለመቀዝቀዝ ይሞክራል። አሁንም ስልክዎን መጠቀም ይችላሉ፣ ነገር ግን ሊንቀራፈፍ ይችላል።\n\nአንዴ ስልክዎ ከቀዘቀዘ በኋላ በመደበኝነት ያሄዳል።"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"መሣሪያዎን ይንቀሉ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"መሣሪያዎ ከኃይል መሙያ ወደቡ አቅራቢያ እየሞቀ ነው። ከኃይል መሙያ ወይም ከዩኤስቢ ተጨማሪ መሣሪያ ጋር ከተገናኘ ይንቀሉት እና ገመዱ የሞቀ ሊሆን ስለሚችል ጥንቃቄ ያድርጉ።"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"የግራ አቋራጭ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"የቀኝ አቋራጭ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 5a35e9023832..7672fc26319f 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"مفعّل"</string> <string name="switch_bar_off" msgid="5669805115416379556">"متوقف"</string> <string name="tile_unavailable" msgid="3095879009136616920">"غير متوفّر"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"مزيد من المعلومات"</string> <string name="nav_bar" msgid="4642708685386136807">"شريط التنقل"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"التنسيق"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"نوع زر اليسار الإضافي"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"يتم تقييد عمل بعض الميزات إلى أن تنخفض درجة حرارة الهاتف.\nانقر للحصول على مزيد من المعلومات."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"سيحاول الهاتف تخفيض درجة حرارته تلقائيًا. سيظل بإمكانك استخدام هاتفك، ولكن قد يعمل بشكل أبطأ.\n\nبعد أن تنخفض درجة حرارة الهاتف، سيستعيد سرعته المعتادة."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"الاطّلاع على خطوات العناية"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"افصِل جهازك"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"تزداد حرارة الجهاز بالقرب من منفذ الشحن. إذا كان الجهاز متصلاً بشاحن أو ملحق USB، عليك فصله وتوخي الحذر لأن درجة حرارة الكابل قد تكون مرتفعة أيضًا."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"الاطّلاع على خطوات العناية"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"اختصار اليسار"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"اختصار اليمين"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 912c82eac52c..990c812d44e3 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"অন"</string> <string name="switch_bar_off" msgid="5669805115416379556">"অফ"</string> <string name="tile_unavailable" msgid="3095879009136616920">"উপলব্ধ নহয়"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"অধিক জানক"</string> <string name="nav_bar" msgid="4642708685386136807">"নেভিগেশ্বন দণ্ড"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"লেআউট"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"বাওঁ বুটামৰ অতিৰিক্ত প্ৰকাৰ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"বাওঁ শ্বৰ্টকাট"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"সোঁ শ্বৰ্টকাট"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index fd2d848cc294..b21061aa427a 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Aktiv"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Deaktiv"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Əlçatan deyil"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ətraflı məlumat"</string> <string name="nav_bar" msgid="4642708685386136807">"Naviqasiya paneli"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Tərtibat"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Əlavə sol düymə növü"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Telefon soyuyana kimi bəzi funksiyalar məhdudlaşdırılır.\nƏtraflı məlumat üçün toxunun"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonunuz avtomatik olaraq soyumağa başlayacaq. Telefon istifadəsinə davam edə bilərsiniz, lakin sürəti yavaşlaya bilər.\n\nTelefonunuz soyuduqdan sonra normal işləyəcək."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ehtiyat tədbiri mərhələlərinə baxın"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızı ayırın"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Cihazınız şarj portunun yaxınlığında qızmağa başlayır. Şarj cihazına və ya USB aksesuarına qoşulubsa, onu ayırın və diqqətli olun, çünki kabel də qıza bilər."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ehtiyat tədbiri mərhələlərinə baxın"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Sol qısayol"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Sağ qısayol"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index dbef1e40eb81..78582cd3b5e4 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string> <string name="nav_bar" msgid="4642708685386136807">"Traka za navigaciju"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Raspored"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Dodatni tip levog dugmeta"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke funkcije su ograničene dok se telefon ne ohladi.\nDodirnite za više informacija"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon će automatski pokušati da se ohladi. I dalje ćete moći da koristite telefon, ali će sporije reagovati.\n\nKada se telefon ohladi, normalno će raditi."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte upozorenja"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Isključite uređaj"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Uređaj se zagreva u blizini porta za punjenje. Ako je povezan sa punjačem ili USB opremom, isključite je i budite pažljivi jer i kabl može da bude vruć."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Pogledajte upozorenja"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Leva prečica"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desna prečica"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 372392c8131d..f1a84bc5f9ea 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Уключана"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Выключана"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Недаступна"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"даведацца больш"</string> <string name="nav_bar" msgid="4642708685386136807">"Панэль навігацыі"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Раскладка"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Дадатковы тып кнопкі \"ўлева\""</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Некаторыя функцыі абмежаваны, пакуль тэлефон не астыне.\nНацісніце, каб даведацца больш"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш тэлефон аўтаматычна паспрабуе астыць. Вы можаце па-ранейшаму карыстацца сваім тэлефонам, але ён можа працаваць больш павольна.\n\nПасля таго як ваш тэлефон астыне, ён будзе працаваць у звычайным рэжыме."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Глядзець паэтапную дапамогу"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Адключыце прыладу"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Ваша прылада моцна награваецца ў месцы, дзе знаходзіцца зарадны порт. Калі яна падключана да зараднай прылады ці USB-прылады, адключыце яе і будзьце асцярожнымі з кабелем, які таксама можа награвацца."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Глядзець паэтапную дапамогу"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ярлык \"улева\""</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Ярлык \"управа\""</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 1bca6a479f0c..46bd5c22939c 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Вкл."</string> <string name="switch_bar_off" msgid="5669805115416379556">"Изкл."</string> <string name="tile_unavailable" msgid="3095879009136616920">"Не е налице"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"научете повече"</string> <string name="nav_bar" msgid="4642708685386136807">"Лента за навигация"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Оформление"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Тип на допълнителния ляв бутон"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Някои функции са ограничени, докато телефонът се охлажда.\nДокоснете за още информация"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонът ви автоматично ще направи опит за охлаждане. Пак можете да го използвате, но той може да работи по-бавно.\n\nСлед като се охлади, ще работи нормално."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Вижте стъпките, които да предприемете"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Изключете устройството си"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Устройството ви се загрява до порта за зареждане. Ако е свързано със зарядно устройство или аксесоар за USB, изключете го и внимавайте, тъй като и кабелът може да е топъл."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Вижте стъпките, които да предприемете"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ляв пряк път"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десен пряк път"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 80beebc2f789..15cd7262e8dc 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ফোন ঠাণ্ডা না হওয়া পর্যন্ত কিছু ফিচার কাজ করে না।\nআরও তথ্যের জন্য ট্যাপ করুন"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপনার ফোনটি নিজে থেকেই ঠাণ্ডা হওয়ার চেষ্টা করবে৷ আপনি তবুও আপনার ফোন ব্যবহার করতে পারেন, কিন্তু এটি একটু ধীরে চলতে পারে৷\n\nআপনার ফোনটি পুরোপুরি ঠাণ্ডা হয়ে গেলে এটি স্বাভাবিকভাবে চলবে৷"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপনার ডিভাইস আনপ্লাগ করা"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"চার্জিং পোর্টের কাছে আপনার ডিভাইসটি গরম হচ্ছে। এটি চার্জার বা ইউএসবি অ্যাক্সেসরির সাথে কানেক্ট করা থাকলে, আনপ্লাগ করুন এবং সতর্ক থাকুন কারণ কেবেলটিও গরম হতে পারে।"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"কী করতে হবে ধাপে ধাপে দেখুন"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"বাঁদিকের শর্টকাট"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ডানদিকের শর্টকাট"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index ecb0e24ad742..8214ce014e95 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigaciona traka"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Raspored"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnog dugmeta lijevo"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke funkcije su ograničene dok se telefon hladi.\nDodirnite za više informacija"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Vaš telefon će se automatski pokušati ohladiti. I dalje možete koristi telefon, ali će možda raditi sporije.\n\nNakon što se ohladi, telefon će normalno raditi."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte korake za zaštitu"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Uređaj se zagrijava u blizini priključka za punjenje. Ako je povezan s punjačem ili USB dodatkom, iskopčajte ga i vodite računa jer i kabl može biti topao."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Prikaz koraka za zaštitu"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Prečica lijevo"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Prečica desno"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index bbffb029361e..7657933b05f6 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -251,7 +251,7 @@ <string name="quick_settings_connecting" msgid="2381969772953268809">"S\'està connectant..."</string> <string name="quick_settings_hotspot_label" msgid="1199196300038363424">"Punt d\'accés Wi-Fi"</string> <string name="quick_settings_hotspot_secondary_label_transient" msgid="7585604088079160564">"S\'està activant…"</string> - <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Economitzador activat"</string> + <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Estalvi dades activat"</string> <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# dispositiu}other{# dispositius}}"</string> <string name="quick_settings_flashlight_label" msgid="4904634272006284185">"Llanterna"</string> <string name="quick_settings_flashlight_camera_in_use" msgid="4820591564526512571">"Càmera en ús"</string> @@ -593,13 +593,12 @@ <string name="accessibility_long_click_tile" msgid="210472753156768705">"Obre la configuració"</string> <string name="accessibility_status_bar_headphones" msgid="1304082414912647414">"Auriculars connectats"</string> <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Auriculars connectats"</string> - <string name="data_saver" msgid="3484013368530820763">"Economitzador de dades"</string> - <string name="accessibility_data_saver_on" msgid="5394743820189757731">"L\'Economitzador de dades està activat"</string> + <string name="data_saver" msgid="3484013368530820763">"Estalvi de dades"</string> + <string name="accessibility_data_saver_on" msgid="5394743820189757731">"L\'Estalvi de dades està activat"</string> <string name="switch_bar_on" msgid="1770868129120096114">"Activat"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desactivat"</string> <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"més informació"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegació"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Disposició"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipus de botó addicional de l\'esquerra"</string> @@ -624,7 +623,7 @@ <string name="right_keycode" msgid="2480715509844798438">"Codi de tecla de la dreta"</string> <string name="left_icon" msgid="5036278531966897006">"Icona de l\'esquerra"</string> <string name="right_icon" msgid="1103955040645237425">"Icona de la dreta"</string> - <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén premut i arrossega per afegir mosaics"</string> + <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén premut i arrossega per afegir icones"</string> <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén premut i arrossega per reorganitzar els mosaics"</string> <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrossega aquí per suprimir"</string> <string name="drag_to_remove_disabled" msgid="933046987838658850">"Necessites com a mínim <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> mosaics"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Algunes funcions estan limitades mentre el telèfon es refreda.\nToca per obtenir més informació"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"El telèfon provarà de refredar-se automàticament. Podràs continuar utilitzant-lo, però és possible que funcioni més lentament.\n\nUn cop s\'hagi refredat, funcionarà amb normalitat."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Mostra els passos de manteniment"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconnecta el dispositiu"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"El dispositiu s\'està escalfant a prop del port de càrrega. Si està connectat a un carregador o a un accessori USB, desconnecta\'l. Ves amb compte perquè el cable també pot haver-se escalfat."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Mostra els pasos de manteniment"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Drecera de l\'esquerra"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Drecera de la dreta"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 66874fdef434..70737ad187af 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Zapnuto"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Vypnuto"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupné"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"další informace"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigační panel"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Rozvržení"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Zvláštní typ tlačítka vlevo"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Některé funkce jsou při chladnutí telefonu omezeny.\nKlepnutím zobrazíte další informace"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon se automaticky pokusí vychladnout. Lze jej nadále používat, ale může být pomalejší.\n\nAž telefon vychladne, bude fungovat normálně."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobrazit pokyny, co dělat"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zařízení"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Zařízení se zahřívá v oblasti nabíjecího portu. Pokud je připojeno k nabíječce nebo příslušenství USB, odpojte ho (dejte pozor, kabel také může být zahřátý)."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobrazit pokyny, co dělat"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Zkratka vlevo"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Zkratka vpravo"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index ab201d7522ac..65228d571bb0 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Til"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Fra"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Ikke tilgængelig"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"få flere oplysninger"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigationslinje"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra venstre knaptype"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Nogle funktioner er begrænsede, mens telefonen køler ned.\nTryk for at få flere oplysninger"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Din telefon forsøger automatisk at køle ned. Du kan stadig bruge telefonen, men den kører muligvis langsommere.\n\nNår din telefon er kølet ned, fungerer den normalt igen."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se håndteringsvejledning"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Træk stikket ud af din enhed"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Din enhed er ved at blive varm i nærheden af opladningsporten. Hvis enheden er tilsluttet en oplader eller USB-enhed, skal du trække stikket ud. Vær opmærksom på, at stikket også kan være varmt."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Se vejledningen i pleje"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Venstre genvej"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Højre genvej"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 5bf907cc5ce6..c0ba5dc3c42b 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"An"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Aus"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nicht verfügbar"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Weitere Informationen"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigationsleiste"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Zusätzlicher linker Schaltflächentyp"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Einige Funktionen sind während der Abkühlphase des Smartphones eingeschränkt.\nFür mehr Informationen tippen."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Dein Smartphone kühlt sich automatisch ab. Du kannst dein Smartphone weiterhin nutzen, aber es reagiert möglicherweise langsamer.\n\nSobald dein Smartphone abgekühlt ist, funktioniert es wieder normal."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Schritte zur Abkühlung des Geräts ansehen"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Gerät vom Stromnetz trennen"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Dein Gerät erwärmt sich am Ladeanschluss. Trenne das Gerät vom Stromnetz, wenn es an ein Ladegerät oder USB-Zubehör angeschlossen ist. Sei vorsichtig, denn das Kabel könnte ebenfalls heiß sein."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Schritte zur Fehlerbehebung ansehen"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Linke Verknüpfung"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Rechte Verknüpfung"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 2a1403cd293e..9fb2726eeaf6 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Ενεργό"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Απενεργοποίηση"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Μη διαθέσιμο"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"μάθετε περισσότερα"</string> <string name="nav_bar" msgid="4642708685386136807">"Γραμμή πλοήγησης"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Διάταξη"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Επιπλέον τύπος αριστερού κουμπιού"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Ορισμένες λειτουργίες περιορίζονται κατά τη μείωση της θερμοκρασίας.\nΠατήστε για περισσότερες πληροφορίες."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Το τηλέφωνό σας θα προσπαθήσει να μειώσει αυτόματα τη θερμοκρασία. Μπορείτε να εξακολουθήσετε να το χρησιμοποιείτε, αλλά είναι πιθανό να λειτουργεί πιο αργά.\n\nΜόλις μειωθεί η θερμοκρασία του τηλεφώνου σας, θα λειτουργεί ξανά κανονικά."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Δείτε βήματα αντιμετώπισης."</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Αποσυνδέστε τη συσκευή"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Η συσκευή έχει αρχίσει να ζεσταίνεται κοντά στη θύρα φόρτισης. Αν είναι συνδεδεμένη σε φορτιστή ή αξεσουάρ USB, αποσυνδέστε την και προσέξτε γιατί και το καλώδιο μπορεί να έχει ζεσταθεί."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Δείτε βήματα αντιμετώπισης"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Αριστερή συντόμευση"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Δεξιά συντόμευση"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 9a01ac8e39ab..144777b30506 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"más información"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Diseño"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón izquierdo adicional"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Algunas funciones se limitan durante el enfriamiento del teléfono.\nPresiona para obtener más información"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Tu teléfono intentará enfriarse automáticamente. Podrás usarlo, pero es posible que funcione más lento.\n\nUna vez que se haya enfriado, volverá a funcionar correctamente."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa el dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"El puerto de carga del dispositivo se está calentando. Si está conectado a un cargador o accesorio USB, desenchúfalo con cuidado, ya que el cable también puede estar caliente."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantenimiento"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Acceso directo izquierdo"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Acceso directo derecho"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 038c7b53c099..1ba2dc603d43 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"más información"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Diseño"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón a la izquierda extra"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Se han limitado algunas funciones mientras el teléfono se enfría.\nToca para ver más información"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"El teléfono intentará enfriarse. Puedes seguir utilizándolo, pero es posible que funcione con mayor lentitud.\n\nUna vez que se haya enfriado, funcionará con normalidad."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa tu dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Tu dispositivo se está calentando cerca del puerto de carga. Si está conectado a un cargador o a un accesorio USB, desenchúfalo con cuidado, ya que el cable también puede estar caliente."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantenimiento"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Acceso directo a la izquierda"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Acceso directo a la derecha"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 731740453430..6c277081204d 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Sees"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Väljas"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Pole saadaval"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"lisateave"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigeerimisriba"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Paigutus"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Täiendava vasaku nupu tüüp"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Mõned funktsioonid on piiratud, kuni telefon jahtub.\nPuudutage lisateabe saamiseks."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Teie telefon proovib automaatselt maha jahtuda. Saate telefoni ikka kasutada, kuid see võib olla aeglasem.\n\nKui telefon on jahtunud, töötab see tavapäraselt."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vaadake hooldusjuhiseid"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Eemaldage seade"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Teie seade läheb laadimispordi juurest soojaks. Kui see on ühendatud laadija või USB-tarvikuga, eemaldage see ja olge ettevaatlik, kuna kaabel võib samuti soe olla."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vaadake hooldusjuhiseid"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vasak otsetee"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Parem otsetee"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index a76109854f3a..2e2d6b55c52f 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Eginbide batzuk ezingo dira erabili telefonoa hoztu arte.\nInformazio gehiago lortzeko, sakatu hau."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonoa automatikoki saiatuko da hozten. Hoztu bitartean, telefonoa erabiltzen jarrai dezakezu, baina mantsoago funtziona lezake.\n\nTelefonoaren tenperatura jaitsi bezain laster, ohi bezala funtzionatzen jarraituko du."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ikusi zaintzeko urratsak"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deskonektatu gailua"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Gailua berotzen ari da kargatzeko atakaren inguruan. Kargagailu edo USB bidezko osagarri batera konektatuta badago, deskonekta ezazu kontuz, kablea ere beroa egongo baita agian."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ikusi zaintzeko urratsak"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ezkerreko lasterbidea"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Eskuineko lasterbidea"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index e7aefda13a00..d2b3f7c2277a 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"روشن"</string> <string name="switch_bar_off" msgid="5669805115416379556">"خاموش"</string> <string name="tile_unavailable" msgid="3095879009136616920">"در دسترس نیست"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"بیشتر بدانید"</string> <string name="nav_bar" msgid="4642708685386136807">"نوار پیمایش"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"طرحبندی"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"نوع دکمه منتهیالیه چپ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"وقتی تلفن درحال خنک شدن است، بعضی از ویژگیها محدود میشوند.\nبرای اطلاعات بیشتر ضربه بزنید"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"تلفنتان بهطور خودکار سعی میکند خنک شود. همچنان میتوانید از تلفنتان استفاده کنید، اما ممکن است کندتر عمل کند.\n\nوقتی تلفن خنک شد، عملکرد عادیاش از سرگرفته میشود."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"دیدن اقدامات محافظتی"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"دستگاه را جدا کنید"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"دستگاهتان کنار درگاه شارژ گرم شده است. اگر دستگاهتان به شارژر یا لوازم جانبی USB متصل است، آن را جدا کنید و مراقب باشید چون ممکن است کابل هم گرم باشد."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"مشاهده مراحل احتیاط"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"میانبر چپ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"میانبر راست"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 21c8dfc17cf0..6817033ee8c1 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Päällä"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Pois päältä"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Ei käytettävissä"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"lukeaksesi lisää"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigointipalkki"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Asettelu"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ylimääräinen vasen painiketyyppi"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Joidenkin ominaisuuksien käyttöä on rajoitettu puhelimen jäähtymisen aikana.\nLue lisää napauttamalla"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Puhelimesi yrittää automaattisesti jäähdyttää itsensä. Voit silti käyttää puhelinta, mutta se voi toimia hitaammin.\n\nKun puhelin on jäähtynyt, se toimii normaalisti."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Katso huoltovaiheet"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Irrota laite"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Laite lämpenee latausportin lähellä. Jos laite on yhdistetty laturiin tai USB-lisälaitteeseen, irrota se varoen, sillä johtokin voi olla lämmin."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Katso huoltovaiheet"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vasen pikakuvake"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Oikea pikakuvake"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index b3439ae969a9..9a3e2d121068 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Certaines fonctionnalités sont limitées pendant que le téléphone refroidit.\nTouchez pour en savoir plus"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Votre téléphone va essayer de se refroidir automatiquement. Vous pouvez toujours l\'utiliser, mais il risque d\'être plus lent.\n\nUne fois refroidi, il fonctionnera normalement."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débranchez votre appareil"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Votre appareil chauffe près du port de recharge. S\'il est connecté à un chargeur ou à un accessoire USB, débranchez-le en faisant attention : le câble pourrait également être chaud."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Afficher les étapes d\'entretien"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Raccourci gauche"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Raccourci droit"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 2ffb38934b5b..df5ded35e2fa 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Activé"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Désactivé"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Indisponible"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"en savoir plus"</string> <string name="nav_bar" msgid="4642708685386136807">"Barre de navigation"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Disposition"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Type de bouton gauche supplémentaire"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Fonctionnalités limitées pendant le refroidissement du téléphone.\nAppuyer pour en savoir plus"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Votre téléphone va essayer de se refroidir automatiquement. Vous pouvez toujours l\'utiliser, mais il risque d\'être plus lent.\n\nUne fois refroidi, il fonctionnera normalement."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débrancher votre appareil"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Votre appareil se réchauffe près du port de recharge. S\'il est connecté à un chargeur ou un accessoire USB, débranchez-le en faisant attention, car le câble peut lui aussi être chaud."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Afficher les étapes d\'entretien"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Raccourci gauche"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Raccourci droit"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 2da351a0dca2..390742035ba2 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Non dispoñible"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"obter máis información"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Deseño"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón adicional á esquerda"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"O uso dalgunhas funcións é limitado mentres o teléfono arrefría.\nToca para obter máis información"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"O teléfono tentará arrefriar automaticamente. Podes utilizalo, pero é probable que funcione máis lento.\n\nUnha vez que arrefríe, funcionará con normalidade."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantemento"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconectar o dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"O dispositivo estase quentando cerca do porto de carga. Se está conectado a un cargador ou a un accesorio USB, desconéctao con coidado, xa que o cable tamén podería estar quente."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantemento"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atallo á esquerda"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atallo á dereita"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 97fed2367825..b362597f9133 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ફોન ઠંડો થાય ત્યાં સુધી અમુક સુવિધાઓ મર્યાદિત હોય છે.\nવધુ માહિતી માટે ટૅપ કરો"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"તમારો ફોન ઑટોમૅટિક રીતે ઠંડો થવાનો પ્રયાસ કરશે. તમે હજી પણ તમારા ફોનનો ઉપયોગ કરી શકો છો, પરંતુ તે કદાચ થોડો ધીમો ચાલે.\n\nતમારો ફોન ઠંડો થઈ જવા પર, તે સામાન્ય રીતે ચાલશે."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"સારસંભાળના પગલાં જુઓ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"તમારા ડિવાઇસને અનપ્લગ કરો"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"તમારું ડિવાઇસ ચાર્જિંગ પોર્ટની પાસે ગરમ થઈ રહ્યું છે. જો તે કોઈ ચાર્જર અથવા USB ઍક્સેસરી સાથે કનેક્ટેડ હોય, તો તેને અનપ્લગ કરો અને ધ્યાન રાખો, કારણ કે કેબલ ગરમ પણ હોઈ શકે છે."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"સારસંભાળના પગલાં જુઓ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ડાબો શૉર્ટકટ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"જમણો શૉર્ટકટ"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 81a11b425fbd..4d9860adb8f9 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"चालू"</string> <string name="switch_bar_off" msgid="5669805115416379556">"बंद"</string> <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध नहीं है"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ज़्यादा जानें"</string> <string name="nav_bar" msgid="4642708685386136807">"नेविगेशन बार"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"कुछ और बाएं बटन के प्रकार"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"फ़ोन के ठंडा होने तक कुछ सुविधाएं काम नहीं करतीं.\nज़्यादा जानकारी के लिए टैप करें"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"आपका फ़ोन अपने आप ठंडा होने की कोशिश करेगा. आप अब भी अपने फ़ोन का उपयोग कर सकते हैं, लेकिन हो सकता है कि यह धीमी गति से चले.\n\nठंडा हो जाने पर आपका फ़ोन सामान्य रूप से चलेगा."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिवाइस के रखरखाव के तरीके देखें"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिवाइस को अनप्लग करें"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"आपका डिवाइस चार्जिंग पोर्ट के पास गर्म हो रहा है. अगर डिवाइस चार्जर या यूएसबी ऐक्सेसरी से कनेक्ट है, तो उसे अनप्लग करें. साथ ही, ध्यान रखें कि केबल भी गर्म हो सकती है."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"प्रबंधन से जुड़े चरण देखें"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"बायां शॉर्टकट"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"दायां शॉर्टकट"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 7ef30b6886c7..b5221487da21 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigacijska traka"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Izgled"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnog lijevog gumba"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke su značajke ograničene dok se telefon ne ohladi.\nDodirnite za više informacija"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon će se automatski pokušati ohladiti. Možete ga nastaviti koristiti, no mogao bi raditi sporije.\n\nKad se ohladi, radit će normalno."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pročitajte upute za održavanje"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Vaš se uređaj zagrijava u blizini priključka za punjenje. Ako je priključen u punjač ili USB uređaj, iskopčajte ga. Pazite jer se i kabel možda zagrijao."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Pogledajte upute za održavanje"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lijevi prečac"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desni prečac"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index b6bfc1b3946e..e7efd9dcb3cd 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Be"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Ki"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nem használható"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"további információ"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigációs sáv"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Elrendezés"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"További bal oldali gombtípus"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Bizonyos funkciók korlátozottan működnek a telefon lehűlése közben.\nKoppintson, ha további információra van szüksége."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"A telefon automatikusan megpróbál lehűlni. Továbbra is tudja használni a telefont, de elképzelhető, hogy működése lelassul.\n\nAmint a telefon lehűl, újra a szokásos módon működik majd."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Olvassa el a kímélő használat lépéseit"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Húzza ki az eszközt"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Eszköze kezd melegedni a töltőport közelében. Ha töltő vagy USB-s kiegészítő van csatlakoztatva hozzá, húzza ki, és legyen óvatos, mert a kábel is meleg lehet."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Olvassa el a megfelelő használat lépéseit"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Bal oldali parancsikon"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Jobb oldali parancsikon"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 038030350c1b..804f350052fc 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Միացված է"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Անջատված է"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Հասանելի չէ"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Իմանալ ավելին"</string> <string name="nav_bar" msgid="4642708685386136807">"Նավիգացիայի գոտի"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Դասավորություն"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Լրացուցիչ ձախ կոճակի տեսակ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Հովանալու ընթացքում հեռախոսի որոշ գործառույթներ սահմանափակ են։\nՀպեք՝ ավելին իմանալու համար։"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ձեր հեռախոսն ավտոմատ կերպով կփորձի hովանալ: Կարող եք շարունակել օգտագործել հեռախոսը, սակայն հնարավոր է, որ այն ավելի դանդաղ աշխատի:\n\nՀովանալուց հետո հեռախոսը կաշխատի կանոնավոր կերպով:"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Քայլեր գերտաքացման ահազանգի դեպքում"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Անջատեք սարքը"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Լիցքավորման միացքի հատվածում սարքը տաքանում է։ Եթե լիցքավորիչի կամ USB լրասարքի է միացված սարքը, անջատեք այն և զգույշ եղեք, քանի որ մալուխը ևս կարող է տաքացած լինել։"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Քայլեր գերտաքացման ահազանգի դեպքում"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ձախ դյուրանցում"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Աջ դյուրանցում"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 163dd9f94f2f..da8b3c0c9010 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Aktif"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Nonaktif"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Tidak tersedia"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"pelajari lebih lanjut"</string> <string name="nav_bar" msgid="4642708685386136807">"Bilah navigasi"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Tata Letak"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Jenis tombol ekstra kiri"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Beberapa fitur dibatasi saat ponsel mendingin.\nKetuk untuk info selengkapnya"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ponsel akan otomatis mencoba mendingin. Anda tetap dapat menggunakan ponsel, tetapi mungkin berjalan lebih lambat.\n\nSetelah dingin, ponsel akan berjalan seperti biasa."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah-langkah perawatan"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut perangkat"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Perangkat menjadi panas saat di dekat port pengisi daya. Jika perangkat terhubung ke pengisi daya atau aksesori USB, cabutlah dan berhati-hatilah karena suhu kabel mungkin juga panas."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Lihat langkah-langkah perawatan"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Pintasan kiri"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pintasan kanan"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index f2a0304a4844..7f18abfe8594 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Kveikt"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Slökkt"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Ekki í boði"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"nánar"</string> <string name="nav_bar" msgid="4642708685386136807">"Yfirlitsstika"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Útlit"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Gerð aukahnapps til vinstri"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Sumir eiginleikar eru takmarkaðir meðan síminn kælir sig.\nÝttu til að fá frekari upplýsingar"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Síminn reynir sjálfkrafa að kæla sig. Þú getur enn notað símann en hann gæti verið hægvirkari.\n\nEftir að síminn hefur kælt sig niður virkar hann eðlilega."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sjá varúðarskref"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Taktu tækið úr sambandi"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Tækið er að hitna nálægt hleðslutenginu. Ef það er tengt við hleðslutæki eða USB-aukahlut skaltu taka það úr sambandi og hafa í huga að snúran gæti einnig verið heit."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Sjá varúðarskref"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Flýtilykill til vinstri"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Flýtilykill til hægri"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 74f3e475555b..83c58bc65dce 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"On"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Off"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Non disponibile"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"scopri di più"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra di navigazione"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo di pulsante extra sinistra"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Alcune funzionalità limitate durante il raffreddamento del telefono.\nTocca per ulteriori informazioni"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Il telefono cercherà automaticamente di raffreddarsi. Puoi comunque usarlo, ma potrebbe essere più lento.\n\nUna volta raffreddato, il telefono funzionerà normalmente."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Leggi le misure da adottare"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Scollega il dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Il tuo dispositivo si sta scaldando vicino alla porta di ricarica. Se è collegato a un caricabatterie o a un accessorio USB, scollegalo e fai attenzione perché il cavo potrebbe essere caldo."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Leggi le misure da adottare"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Scorciatoia sinistra"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Scorciatoia destra"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 4e0d5cebc030..09d5d69716ee 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ჩართული"</string> <string name="switch_bar_off" msgid="5669805115416379556">"გამორთვა"</string> <string name="tile_unavailable" msgid="3095879009136616920">"მიუწვდომელი"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"შეიტყვეთ მეტი"</string> <string name="nav_bar" msgid="4642708685386136807">"ნავიგაციის ზოლი"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"განლაგება"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"მარცხენა დამატებითი ღილაკის ტიპი"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ზოგიერთი ფუნქცია შეზღუდული იქნება, სანამ ტელეფონი გაგრილდება.\nშეეხეთ დამატებითი ინფორმაციის მისაღებად"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"თქვენი ტელეფონი გაგრილებას ავტომატურად შეეცდება. შეგიძლიათ გააგრძელოთ მისით სარგებლობა, თუმცა ტელეფონმა შეიძლება უფრო ნელა იმუშაოს.\n\nგაგრილების შემდგომ ის ჩვეულებრივად იმუშავებს."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"მისაღები ზომების გაცნობა"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"გამოაერᲗეᲗ Თქვენი მოწყობილობა"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"თქვენი მოწყობილობა ხურდება დამტენის პორტთან ახლოს. თუ ის დაკავშირებულია დამტენთან ან USB აქსესუართან, გამორთეთ იგი და იზრუნეთ, რადგან შესაძლოა კაბელიც გახურებული იყოს."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"მისაღები ზომების გაცნობა"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"მარცხენა მალსახმობი"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"მარჯვენა მალსახმობი"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 10c47e39164b..1183e381d04e 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -161,10 +161,8 @@ <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Бет танылмады. Орнына саусақ ізін пайдаланыңыз."</string> <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) --> <skip /> - <!-- no translation found for keyguard_face_failed (9044619102286917151) --> - <skip /> - <!-- no translation found for keyguard_suggest_fingerprint (8742015961962702960) --> - <skip /> + <string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string> + <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string> <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string> @@ -198,9 +196,9 @@ <string name="accessibility_quick_settings_less_time" msgid="9110364286464977870">"Азырақ уақыт."</string> <string name="accessibility_casting_turned_off" msgid="1387906158563374962">"Экранды трансляциялау тоқтатылды."</string> <string name="accessibility_brightness" msgid="5391187016177823721">"Дисплей жарықтығы"</string> - <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Мобильдік деректер кідіртілді"</string> + <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Мобильдік интернет кідіртілді"</string> <string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Деректер кідіртілді"</string> - <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"Белгіленген деректер шегіне жеттіңіз. Мобильдік деректер енді пайдаланылмайды.\n\nЕгер жалғастырсаңыз, деректер трафигі үшін ақы алынуы мүмкін."</string> + <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"Белгіленген деректер шегіне жеттіңіз. Мобильдік интернет енді пайдаланылмайды.\n\nЕгер жалғастырсаңыз, деректер трафигі үшін ақы алынуы мүмкін."</string> <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Жалғастыру"</string> <string name="accessibility_location_active" msgid="2845747916764660369">"Орын өтініштері қосылған"</string> <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Датчиктер өшірулі."</string> @@ -257,7 +255,7 @@ <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# құрылғы}other{# құрылғы}}"</string> <string name="quick_settings_flashlight_label" msgid="4904634272006284185">"Қалта шам"</string> <string name="quick_settings_flashlight_camera_in_use" msgid="4820591564526512571">"Камера қолданылып жатыр"</string> - <string name="quick_settings_cellular_detail_title" msgid="792977203299358893">"Мобильдік деректер"</string> + <string name="quick_settings_cellular_detail_title" msgid="792977203299358893">"Мобильдік интернет"</string> <string name="quick_settings_cellular_detail_data_usage" msgid="6105969068871138427">"Дерек шығыны"</string> <string name="quick_settings_cellular_detail_remaining_data" msgid="1136599216568805644">"Қалған деректер"</string> <string name="quick_settings_cellular_detail_over_limit" msgid="4561921367680636235">"Шектен асу"</string> @@ -600,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Қосулы"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Өшірулі"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Қолжетімді емес"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"толығырақ"</string> <string name="nav_bar" msgid="4642708685386136807">"Шарлау тақтасы"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Формат"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Қосымша сол жақ түйме түрі"</string> @@ -674,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Телефон толық суығанға дейін, кейбір функциялардың жұмысы шектеледі.\nТолығырақ ақпарат үшін түртіңіз."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефон автоматты түрде суи бастайды. Оны пайдалана бере аласыз, бірақ ол баяуырақ жұмыс істеуі мүмкін.\n\nТелефон суығаннан кейін, оның жұмысы қалпына келеді."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Пайдалану нұсқаулығын қараңыз"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Құрылғыны ажыратыңыз"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Құрылғының зарядтау ұяшығы тұрған бөлігі қызып келеді. Зарядтағышқа немесе USB құрылғысына жалғанған болса, оны ажыратыңыз. Абайлаңыз, кабель де ыстық болуы мүмкін."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Пайдалану нұсқаулығын қараңыз"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Сол жақ таңбаша"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Оң жақ таңбаша"</string> @@ -717,7 +712,7 @@ <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"Мазаламау режимі автоматты ереже немесе қолданба арқылы қосылды."</string> <string name="running_foreground_services_title" msgid="5137313173431186685">"Фонда жұмыс істеп тұрған қолданбалар"</string> <string name="running_foreground_services_msg" msgid="3009459259222695385">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string> - <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік деректер өшірілсін бе?"</string> + <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string> <string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 7ee2a9399fb9..c30c2a26c595 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"បើក"</string> <string name="switch_bar_off" msgid="5669805115416379556">"បិទ"</string> <string name="tile_unavailable" msgid="3095879009136616920">"មិនអាចប្រើបាន"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ស្វែងយល់បន្ថែម"</string> <string name="nav_bar" msgid="4642708685386136807">"របាររុករក"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ប្លង់"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ប្រភេទប៊ូតុងខាងឆ្វេងបន្ថែម"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"មុខងារមួយចំនួននឹងមិនអាចប្រើបានពេញលេញនោះទេ ខណៈពេលដែលទូរសព្ទកំពុងបញ្ចុះកម្ដៅ។\nសូមចុចដើម្បីទទួលបានព័ត៌មានបន្ថែម"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ទូរសព្ទរបស់អ្នកនឹងព្យាយាមបញ្ចុះកម្តៅដោយស្វ័យប្រវត្តិ។ អ្នកនៅតែអាចប្រើទូរសព្ទរបស់អ្នកបានដដែល ប៉ុន្តែវានឹងដំណើរការយឺតជាងមុន។\n\nបន្ទាប់ពីទូរសព្ទរបស់អ្នកត្រជាក់ជាងមុនហើយ វានឹងដំណើរការដូចធម្មតា។"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"មើលជំហានថែទាំ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ដកឧបករណ៍របស់អ្នក"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ឧបករណ៍របស់អ្នកកំពុងឡើងកម្ដៅនៅជិតរន្ធសាកថ្ម។ ប្រសិនបើឧបករណ៍នេះត្រូវបានភ្ជាប់ទៅឆ្នាំងសាក ឬគ្រឿងបរិក្ខារ USB សូមដកវា និងមានការប្រុងប្រយ័ត្ន ដោយសារខ្សែក៏អាចក្ដៅផងដែរ។"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"មើលជំហានថែទាំ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ផ្លូវកាត់ខាងឆ្វេង"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ផ្លូវកាត់ខាងស្តាំ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 2aec2e305fa4..f32d00a6cc6a 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ಆನ್"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ಆಫ್"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ಲಭ್ಯವಿಲ್ಲ"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string> <string name="nav_bar" msgid="4642708685386136807">"ನ್ಯಾವಿಗೇಷನ್ ಬಾರ್"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ಲೇಔಟ್"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ಹೆಚ್ಚುವರಿ ಎಡ ಬಟನ್ ವಿಧ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ಫೋನ್ ತಣ್ಣಗಾಗುವವರೆಗೂ ಕೆಲವು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಸೀಮಿತಗೊಳಿಸಲಾಗುತ್ತದೆ\nಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ನಿಮ್ಮ ಫೋನ್ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ತಣ್ಣಗಾಗಲು ಪ್ರಯತ್ನಿಸುತ್ತದೆ. ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೀವು ಈಗಲೂ ಬಳಸಬಹುದಾಗಿರುತ್ತದೆ, ಆದರೆ ಇದು ನಿಧಾನವಾಗಿರಬಹುದು.\n\nಒಮ್ಮೆ ನಿಮ್ಮ ಫೋನ್ ತಣ್ಣಗಾದ ನಂತರ ಇದು ಸಾಮಾನ್ಯ ರೀತಿಯಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್ಪ್ಲಗ್ ಮಾಡಿ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ಚಾರ್ಜಿಂಗ್ ಪೋರ್ಟ್ ಸಮೀಪದಲ್ಲಿ ನಿಮ್ಮ ಸಾಧನವು ಬಿಸಿಯಾಗುತ್ತಿದೆ. ಅದನ್ನು ಚಾರ್ಜರ್ ಅಥವಾ USB ಪರಿಕರಕ್ಕೆ ಕನೆಕ್ಟ್ ಮಾಡಿದ್ದರೆ, ಅದನ್ನು ಅನ್ಪ್ಲಗ್ ಮಾಡಿ ಹಾಗೂ ಕೇಬಲ್ ಕೂಡ ಬಿಸಿಯಾಗಿರಬಹುದು ಆದ್ದರಿಂದ ಎಚ್ಚರಿಕೆ ವಹಿಸಿ."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ಕಾಳಜಿ ಹಂತಗಳನ್ನು ನೋಡಿ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ಎಡ ಶಾರ್ಟ್ಕಟ್"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ಬಲ ಶಾರ್ಟ್ಕಟ್"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index b33d766e1986..72e837b39ef2 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"사용"</string> <string name="switch_bar_off" msgid="5669805115416379556">"사용 안함"</string> <string name="tile_unavailable" msgid="3095879009136616920">"사용할 수 없음"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"자세히 알아보기"</string> <string name="nav_bar" msgid="4642708685386136807">"탐색 메뉴"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"레이아웃"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"추가 왼쪽 버튼 유형"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"휴대전화 온도를 낮추는 동안 일부 기능이 제한됩니다.\n자세히 알아보려면 탭하세요."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"휴대전화 온도를 자동으로 낮추려고 시도합니다. 휴대전화를 계속 사용할 수는 있지만 작동이 느려질 수도 있습니다.\n\n휴대전화 온도가 낮아지면 정상적으로 작동됩니다."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"해결 방법 확인하기"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"기기 분리하기"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"기기의 충전 포트 주변 온도가 상승하고 있습니다. 충전기나 USB 액세서리가 연결된 상태라면 분리하세요. 이때 케이블도 뜨거울 수 있으므로 주의하시기 바랍니다."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"취해야 할 조치 확인"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"왼쪽 바로가기"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"오른쪽 바로가기"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index b352277792a9..e6e3818f1c4f 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Күйүк"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Өчүк"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Жеткиликсиз"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"кеңири маалымат"</string> <string name="nav_bar" msgid="4642708685386136807">"Чабыттоо тилкеси"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Калып"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Сол жактагы кошумча баскычтын түрү"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Телефон сууганча айрым элементтердин иши чектелген.\nКеңири маалымат алуу үчүн таптап коюңуз"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонуңуз автоматтык түрдө сууйт. Аны колдоно берсеңиз болот, бирок ал жайыраак иштеп калат.\n\nТелефонуңуз суугандан кийин адаттагыдай эле иштеп баштайт."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Тейлөө кадамдарын көрүңүз"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Түзмөктү сууруп коюңуз"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Түзмөгүңүздүн кубаттоо порту жылып баратат. Эгер түзмөгүңүз кубаттагычка же USB кабелине туташып турса, аны сууруп коюңуз. Абайлаңыз, кабель да жылуу болушу мүмкүн."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Тейлөө кадамдарын көрүңүз"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Сол жактагы кыска жол"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Оң жактагы кыска жол"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index f90e869e7b27..5a97ca5dbe83 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ເປີດ"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ປິດ"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ບໍ່ສາມາດໃຊ້ໄດ້"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ສຶກສາເພີ່ມເຕີມ"</string> <string name="nav_bar" msgid="4642708685386136807">"ແຖບນຳທາງ"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ຮູບແບບ"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ປະເພດປຸ່ມຊ້າຍພິເສດ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ຄຸນສົມບັດບາງຢ່າງຖືກຈຳກັດໄວ້ໃນເວລາຫຼຸດອຸນຫະພູມຂອງໂທລະສັບ.\nແຕະເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ໂທລະສັບຂອງທ່ານຈະພະຍາຍາມລົດອຸນຫະພູມລົງ. ທ່ານຍັງຄົງສາມາດໃຊ້ໂທລະສັບຂອງທ່ານໄດ້ຢູ່, ແຕ່ມັນຈະເຮັດວຽກຊ້າລົງ.\n\nເມື່ອໂທລະສັບຂອງທ່ານບໍ່ຮ້ອນຫຼາຍແລ້ວ, ມັນຈະກັບມາເຮັດວຽກຕາມປົກກະຕິ."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ຖອດອຸປະກອນຂອງທ່ານອອກ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ອຸປະກອນຂອງທ່ານຈະອຸ່ນຂຶ້ນເມື່ອຢູ່ໃກ້ຊ່ອງສາກໄຟ. ຫາກມັນເຊື່ອມຕໍ່ຫາສາຍສາກ ຫຼື ອຸປະກອນເສີມ USB ໃດໜຶ່ງຢູ່, ໃຫ້ຖອດມັນອອກ ແລະ ລະວັງເນື່ອງຈາກສາຍກໍອາດຈະອຸ່ນເຊັ່ນກັນ."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ປຸ່ມລັດຊ້າຍ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ປຸ່ມລັດຂວາ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index fdf45707ca43..0e641c7b3c15 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Įjungta"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Išjungta"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nepasiekiama"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"sužinoti daugiau"</string> <string name="nav_bar" msgid="4642708685386136807">"Naršymo juosta"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Išdėstymas"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Papildomo mygtuko kairėje tipas"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Kai kurios funkcijos gali neveikti, kol telefonas vėsta.\nPalietę gausite daugiau informacijos"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonas automatiškai bandys atvėsti. Telefoną vis tiek galėsite naudoti, tačiau jis gali veikti lėčiau.\n\nKai telefonas atvės, jis veiks įprastai."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Žr. priežiūros veiksmus"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atjunkite įrenginį"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Įrenginys kaista šalia įkrovimo prievado. Jei jis prijungtas prie kroviklio ar USB priedo, atjunkite jį ir patikrinkite, nes laidas taip pat gali būti įkaitęs."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Žr. priežiūros veiksmus"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Spartusis klavišas kairėje"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Spartusis klavišas dešinėje"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 9ca7da534145..07ae2c948a97 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Ieslēgts"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Izslēgts"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nav pieejams"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"uzzinātu vairāk"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigācijas josla"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Izkārtojums"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Kreisās puses papildu pogas veids"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Dažas funkcijas ir ierobežotas, kamēr notiek tālruņa atdzišana.\nPieskarieties, lai uzzinātu vairāk."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Jūsu tālrunis automātiski mēģinās atdzist. Jūs joprojām varat izmantot tālruni, taču tas, iespējams, darbosies lēnāk.\n\nTiklīdz tālrunis būs atdzisis, tas darbosies normāli."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Skatīt apkopes norādījumus"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atvienojiet savu ierīci"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Jūsu ierīce uzkarst, atrodoties uzlādes pieslēgvietas tuvumā. Ja ierīce ir pievienota lādētājam vai USB piederumam, uzmanīgi atvienojiet to, jo arī vads var būt uzkarsis."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Skatīt apkopes norādījumus"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Saīsne kreisajā pusē"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Saīsne labajā pusē"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index a241e54cd855..dc12d0905d98 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Некои функции се ограничени додека телефонот се лади.\nДопрете за повеќе информации"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонот автоматски ќе се обиде да се олади. Вие сепак ќе може да го користите, но тој може да работи побавно.\n\nОткако ќе се олади, ќе работи нормално."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Прикажи ги чекорите за грижа за уредот"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Исклучете го уредот од кабел"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Вашиот уред се загрева во близина на портата за полнење. Ако е поврзан со полнач или USB-додаток, исклучете го од него и внимавајте бидејќи кабелот може да е топол."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Прикажи ги чекорите за грижа за уредот"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Лева кратенка"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десна кратенка"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 4ff230181581..17fa9ea3536b 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ഓൺ"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ഓഫ്"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ലഭ്യമല്ല"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"കൂടുതലറിയുക"</string> <string name="nav_bar" msgid="4642708685386136807">"നാവിഗേഷൻ ബാർ"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ലേഔട്ട്"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"അധിക ഇടത് ബട്ടൺ തരം"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ഫോൺ തണുത്തുകൊണ്ടിരിക്കുമ്പോൾ ചില ഫീച്ചറുകൾ പരിമിതപ്പെടുത്തപ്പെടും.\nകൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പ് ചെയ്യുക"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"നിങ്ങളുടെ ഫോൺ സ്വയമേവ തണുക്കാൻ ശ്രമിക്കും. നിങ്ങൾക്ക് അപ്പോഴും ഫോൺ ഉപയോഗിക്കാമെങ്കിലും പ്രവർത്തനം മന്ദഗതിയിലായിരിക്കും.\n\nതണുത്തുകഴിഞ്ഞാൽ, ഫോൺ സാധാരണ ഗതിയിൽ പ്രവർത്തിക്കും."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ഉപകരണം അൺപ്ലഗ് ചെയ്യുക"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ചാർജിംഗ് പോർട്ടിന് സമീപം നിങ്ങളുടെ ഉപകരണം ചൂടാകുന്നുണ്ട്. ഇത് ചാർജറിലേക്കോ USB ആക്സസറിയിലേക്കോ കണക്റ്റ് ചെയ്തിട്ടുണ്ടെങ്കിൽ അൺപ്ലഗ് ചെയ്യുക, കേബിളും ചൂടായിരിക്കാമെന്നതിനാൽ ശ്രദ്ധിക്കണം."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"മുൻകരുതൽ നടപടികൾ കാണുക"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ഇടത് കുറുക്കുവഴി"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"വലത് കുറുക്കുവഴി"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index a3d13bd447de..751ef19222f4 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Утсыг хөрөх үед зарим онцлогийг хязгаарлана.\nДэлгэрэнгүй мэдээлэл авах бол товшино уу"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Таны утас автоматаар хөрөх болно. Та утсаа ашиглаж болох хэдий ч удаан ажиллаж болзошгүй.\n\nТаны утас хөрсний дараагаар хэвийн ажиллана."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Хянамж болгоомжийн алхмыг харах"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Төхөөрөмжөө салгана уу"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Таны төхөөрөмж цэнэглэх портын ойролцоо халж байна. Хэрэв төхөөрөмжийг цэнэглэгч эсвэл USB дагалдах хэрэгсэлд холбосон бол төхөөрөмжийг салгаж, кабель нь халуун байж болзошгүй тул болгоомжтой байгаарай."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Хянамж болгоомжийн алхмыг харна уу"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Зүүн товчлол"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Баруун товчлол"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index c9f3bc27cdc2..ab8d9527b883 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -327,9 +327,9 @@ <string name="do_disclosure_generic" msgid="4896482821974707167">"हे डिव्हाइस तुमच्या संस्थेचे आहे"</string> <string name="do_disclosure_with_name" msgid="2091641464065004091">"हे डिव्हाइस <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> चे आहे"</string> <string name="do_financed_disclosure_with_name" msgid="6723004643314467864">"हे डिव्हाइस <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> द्वारे पुरवले गेले आहे"</string> - <string name="phone_hint" msgid="6682125338461375925">"फोनसाठी चिन्हावरून स्वाइप करा"</string> - <string name="voice_hint" msgid="7476017460191291417">"व्हॉइस सहाय्यासाठी चिन्हावरून स्वाइप करा"</string> - <string name="camera_hint" msgid="4519495795000658637">"कॅमेर्यासाठी चिन्हावरून स्वाइप करा"</string> + <string name="phone_hint" msgid="6682125338461375925">"फोनसाठी आयकनवरून स्वाइप करा"</string> + <string name="voice_hint" msgid="7476017460191291417">"व्हॉइस सहाय्यासाठी आयकनवरून स्वाइप करा"</string> + <string name="camera_hint" msgid="4519495795000658637">"कॅमेर्यासाठी आयकनवरून स्वाइप करा"</string> <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"संपूर्ण शांतता. हे स्क्रीन रीडर ना देखील शांत करेल."</string> <string name="interruption_level_none" msgid="219484038314193379">"संपूर्ण शांतता"</string> <string name="interruption_level_priority" msgid="661294280016622209">"केवळ प्राधान्य"</string> @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"सुरू"</string> <string name="switch_bar_off" msgid="5669805115416379556">"बंद"</string> <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध नाही"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"अधिक जाणून घ्या"</string> <string name="nav_bar" msgid="4642708685386136807">"नॅव्हिगेशन बार"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"अतिरिक्त डाव्या बटणाचा प्रकार"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"फोन थंड होईपर्यंत काही वैशिष्ट्ये मर्यादित केली.\nअधिक माहितीसाठी टॅप करा"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"तुमचा फोन स्वयंचलितपणे थंड होईल. तुम्ही अद्यापही तुमचा फोन वापरू शकता परंतु तो कदाचित धीमेपणे कार्य करेल.\n\nतुमचा फोन एकदा थंड झाला की, तो सामान्यपणे कार्य करेल."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"काय काळजी घ्यावी ते पहा"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"तुमचे डिव्हाइस अनप्लग करा"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"तुमचे डिव्हाइस हे चार्जिंग पोर्टच्या जवळ गरम होत आहे. हे चार्जर किंवा USB अॅक्सेसरी यांच्याशी कनेक्ट केलेले असल्यास, ते अनप्लग करा आणि काळजी घ्या कारण केबलदेखील गरम असू शकते."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"काय काळजी घ्यावी ते पहा"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"डावा शॉर्टकट"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"उजवा शॉर्टकट"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index b161345a3d9c..11b788c1ae6e 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Hidup"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Mati"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Tidak tersedia"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"mengetahui lebih lanjut"</string> <string name="nav_bar" msgid="4642708685386136807">"Bar navigasi"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Reka letak"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Jenis butang kiri tambahan"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Sesetengah ciri adalah terhad semasa telefon menyejuk.\nKetik untuk mendapatkan maklumat lanjut"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon anda akan cuba menyejuk secara automatik. Anda masih dapat menggunakan telefon itu tetapi telefon tersebut mungkin berjalan lebih perlahan.\n\nSetelah telefon anda sejuk, telefon itu akan berjalan seperti biasa."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah penjagaan"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut palam peranti anda"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Peranti anda menjadi panas berdekatan port pengecasan. Jika peranti anda disambungkan ke pengecas atau aksesori USB, cabut palam peranti dan berhati-hati kerana kabel juga mungkin panas."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Lihat langkah penjagaan"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Pintasan kiri"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pintasan kanan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 3f201ac37ded..e87be582afaf 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ဖွင့်"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ပိတ်"</string> <string name="tile_unavailable" msgid="3095879009136616920">"မရနိုင်ပါ"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ပိုမိုလေ့လာရန်"</string> <string name="nav_bar" msgid="4642708685386136807">"ရွှေ့လျားရန်ဘားတန်း"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"အပြင်အဆင်"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"လက်ဝဲခလုတ် အမျိုးအစားအပို"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ဖုန်းကို အေးအောင်ပြုလုပ်နေစဉ်တွင် အချို့ဝန်ဆောင်မှုများကို ကန့်သတ်ထားပါသည်။\nနောက်ထပ်အချက်အလက်များအတွက် တို့ပါ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"သင့်ဖုန်းသည် အလိုအလျောက် ပြန်အေးသွားပါလိမ့်မည်။ ဖုန်းကို အသုံးပြုနိုင်ပါသေးသည် သို့သော် ပိုနှေးနိုင်ပါသည်။\n\nသင့်ဖုန်း အေးသွားသည်နှင့် ပုံမှန်အတိုင်း ပြန်အလုပ်လုပ်ပါလိမ့်မည်။"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"သင့်စက်ကို ပလတ်ဖြုတ်ပါ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"သင့်စက်သည် အားသွင်းပို့တ်အနီးတွင် ပူနွေးလာသည်။ ၎င်းကို အားသွင်းကိရိယာ (သို့) USB ဆက်စပ်ပစ္စည်းနှင့် ချိတ်ဆက်ထားပါက ပလတ်ဖြုတ်ပါ။ ကြိုးကလည်း ပူနွေးနေနိုင်သဖြင့် ဂရုပြုပါ။"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"လက်ဝဲ ဖြတ်လမ်းလင့်ခ်"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"လက်ယာ ဖြတ်လမ်းလင့်ခ်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 24dcef7fdf76..22f3836fb2b4 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"På"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Av"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Utilgjengelig"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"finne ut mer"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigasjonsrad"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Oppsett"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra venstre-knapptype"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Enkelte funksjoner er begrenset mens telefonen kjøles ned.\nTrykk for å se mer informasjon"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonen din kommer til å prøve å kjøle seg ned automatisk. Du kan fremdeles bruke telefonen, men den kjører muligens saktere.\n\nTelefonen kommer til å kjøre som normalt, når den har kjølt seg ned."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se vedlikeholdstrinnene"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koble fra enheten"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Enheten begynner å bli varm nær ladeporten. Hvis den er koblet til en lader eller et USB-tilbehør, må du koble den fra. Vær forsiktig da kabelen også kan være varm."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Se vedlikeholdstrinnene"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Venstre hurtigtast"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Høyre hurtigtast"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 4f85e3e9c0d0..eb7876589c56 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"अन छ"</string> <string name="switch_bar_off" msgid="5669805115416379556">"अफ छ"</string> <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध छैन"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"थप जान्नुहोस्"</string> <string name="nav_bar" msgid="4642708685386136807">"नेभिगेशन पट्टी"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"अतिरिक्त बायाँतिरको बटनको प्रकार"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"फोन नचिस्सिँदासम्म केही सुविधाहरू उपलब्ध हुने छैनन्।\nथप जानकारीका लागि ट्याप गर्नुहोस्"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"तपाईंको फोन स्वतः चिसो हुने प्रयास गर्ने छ। तपाईं अझै पनि आफ्नो फोनको प्रयोग गर्न सक्नुहुन्छ तर त्यो अझ ढिलो चल्न सक्छ।\n\nचिसो भएपछि तपाईंको फोन सामान्य गतिमा चल्नेछ।"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिभाइस बिजुलीको स्रोतबाट निकाल्नुहोस्"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"तपाईंको डिभाइसको चार्जिङ पोर्टतिरको भाग तातीरहेको छ। तपाईंको डिभाइस चार्जर वा USB एक्सेसरीमा जोडिएको गरिएको छ भने त्यसलाई निकाल्नुहोस्। यसका साथै सो केबल तातो हुन सक्ने भएकाले ख्याल गर्नुहोला।"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"हेरचाहसम्बन्धी चरणहरू हेर्नुहोस्"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"बायाँतिरको सर्टकट"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"दायाँतिरको सर्टकट"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index ee08b0146965..c6c63b566412 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Bepaalde functies zijn beperkt terwijl de telefoon afkoelt.\nTik voor meer informatie"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Je telefoon probeert automatisch af te koelen. Je kunt je telefoon nog steeds gebruiken, maar deze kan langzamer werken.\n\nZodra de telefoon is afgekoeld, werkt deze weer normaal."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Onderhoudsstappen bekijken"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Je apparaat loskoppelen"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Je apparaat wordt warm in de buurt van de oplaadpoort. Als het apparaat is aangesloten op een oplader of USB-poort, koppel je het los. Wees voorzichtig: de kabel kan warm zijn."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Onderhoudsstappen bekijken"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Snelkoppeling links"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Snelkoppeling rechts"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 6923145f93b5..d6f742c001e3 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -178,7 +178,7 @@ <string name="accessibility_overflow_action" msgid="8555835828182509104">"ସମସ୍ତ ବିଜ୍ଞପ୍ତି ଦେଖନ୍ତୁ"</string> <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ଟେଲି-ଟାଇପରାଇଟର୍ ସକ୍ଷମ ଅଛି।"</string> <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ରିଙ୍ଗର୍ କମ୍ପନରେ ଅଛି।"</string> - <string name="accessibility_ringer_silent" msgid="8994620163934249882">"ରିଙ୍ଗର୍ ସାଇଲେଣ୍ଟରେ ଅଛି।"</string> + <string name="accessibility_ringer_silent" msgid="8994620163934249882">"ରିଂଗର ସାଇଲେଣ୍ଟରେ ଅଛି।"</string> <!-- no translation found for accessibility_casting (8708751252897282313) --> <skip /> <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"ବିଜ୍ଞପ୍ତି ଶେଡ୍।"</string> @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ଚାଲୁ ଅଛି"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ବନ୍ଦ ଅଛି"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ଅନୁପଲବ୍ଧ"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ଅଧିକ ଜାଣନ୍ତୁ"</string> <string name="nav_bar" msgid="4642708685386136807">"ନାଭିଗେଶନ୍ ବାର୍"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ଲେଆଉଟ୍"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ସମ୍ପୂର୍ଣ୍ଣ ବାମ ବଟନ୍ ପ୍ରକାର"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ଫୋନ୍ ଥଣ୍ଡା ହେବା ସମୟରେ କିଛି ଫିଚର୍ ଠିକ ଭାବେ କାମ କରିନଥାଏ।\nଅଧିକ ସୂଚନା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ଆପଣଙ୍କ ଫୋନ୍ ସ୍ୱଚାଳିତ ଭାବେ ଥଣ୍ଡା ହେବାକୁ ଚେଷ୍ଟା କରିବ। ଆପଣ ତଥାପି ନିଜ ଫୋନ୍ ବ୍ୟବହାର କରିପାରିବେ, କିନ୍ତୁ ଏହା ଧୀରେ ଚାଲିପାରେ।\n\nଆପଣଙ୍କ ଫୋନ୍ ଥଣ୍ଡା ହୋଇଯିବାପରେ, ଏହା ସାମାନ୍ୟ ଭାବେ ଚାଲିବ।"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନପ୍ଲଗ କରନ୍ତୁ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ଚାର୍ଜିଂ ପୋର୍ଟ ନିକଟରେ ଆପଣଙ୍କ ଡିଭାଇସ ଗରମ ହୋଇଯାଉଛି। ଯଦି ଏହା ଏକ ଚାର୍ଜର କିମ୍ବା USB ଆକସେସୋରୀ ସହ କନେକ୍ଟ କରାଯାଇଥାଏ ତେବେ ଏହାକୁ ଅନପ୍ଲଗ କରନ୍ତୁ ଏବଂ ଧ୍ୟାନ ରଖନ୍ତୁ କାରଣ କେବୁଲ ମଧ୍ୟ ଗରମ ହୋଇପାରେ।"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ସେବା ସମ୍ବନ୍ଧିତ ଷ୍ଟେପ୍ଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ବାମ ଶର୍ଟକଟ୍"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ଡାହାଣ ଶର୍ଟକଟ୍"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 2beeb5969b1b..df36b540f003 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ਚਾਲੂ"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ਬੰਦ"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ਅਣਉਪਲਬਧ"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ਹੋਰ ਜਾਣੋ"</string> <string name="nav_bar" msgid="4642708685386136807">"ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"ਖਾਕਾ"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ਵਧੇਰੇ ਖੱਬੇ ਬਟਨ ਕਿਸਮ"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ਫ਼ੋਨ ਦੇ ਠੰਡਾ ਹੋਣ ਦੇ ਦੌਰਾਨ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਸੀਮਤ ਹੁੰਦੀਆਂ ਹਨ।\nਵਧੇਰੇ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ਤੁਹਾਡਾ ਫ਼ੋਨ ਸਵੈਚਲਿਤ ਰੂਪ ਵਿੱਚ ਠੰਡਾ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੇਗਾ। ਤੁਸੀਂ ਹਾਲੇ ਵੀ ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਵਰਤ ਸਕਦੇ ਹੋ, ਪਰੰਤੂ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਵਧੇਰੇ ਹੌਲੀ ਚੱਲੇ।\n\nਇੱਕ ਵਾਰ ਠੰਡਾ ਹੋਣ ਤੋਂ ਬਾਅਦ ਤੁਹਾਡਾ ਫ਼ੋਨ ਸਧਾਰਨ ਤੌਰ \'ਤੇ ਚੱਲੇਗਾ।"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ਆਪਣਾ ਡੀਵਾਈਸ ਅਣਪਲੱਗ ਕਰੋ"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਚਾਰਜਿੰਗ ਪੋਰਟ ਦੇ ਨੇੜੇ ਗਰਮ ਹੋ ਰਿਹਾ ਹੈ। ਜੇ ਇਹ ਕਿਸੇ ਚਾਰਜਰ ਜਾਂ USB ਐਕਸੈਸਰੀ ਨਾਲ ਕਨੈਕਟ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਅਣਪਲੱਗ ਕਰੋ ਅਤੇ ਸਾਵਧਾਨ ਰਹੋ, ਕਿਉਂਕਿ ਕੇਬਲ ਵੀ ਗਰਮ ਹੋ ਸਕਦੀ ਹੈ।"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ਖੱਬਾ ਸ਼ਾਰਟਕੱਟ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ਸੱਜਾ ਸ਼ਾਰਟਕੱਟ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 9449f267a2e0..f66eff3098db 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Włączono"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Wyłączono"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Niedostępne"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Więcej informacji"</string> <string name="nav_bar" msgid="4642708685386136807">"Pasek nawigacji"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Układ"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Typ dodatkowego lewego przycisku"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Podczas obniżania temperatury telefonu niektóre funkcje są ograniczone\nKliknij, by dowiedzieć się więcej"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon automatycznie podejmie próbę obniżenia temperatury. Możesz go wciąż używać, ale telefon może działać wolniej.\n\nGdy temperatura się obniży, telefon będzie działał normalnie."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobacz instrukcję postępowania"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odłącz urządzenie"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Urządzenie za bardzo się nagrzewa w okolicy gniazda ładowania. Jeśli jest podłączone do ładowarki albo akcesorium USB, odłącz je. Uważaj, bo kabel również może być nagrzany."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobacz instrukcję postępowania"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lewy skrót"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Prawy skrót"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 2f9a36e64f29..6ce4ccb37870 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo extra"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Alguns recursos ficam limitados enquanto o smartphone é resfriado.\nToque para saber mais"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Seu smartphone tentará se resfriar automaticamente. Você ainda poderá usá-lo, mas talvez ele fique mais lento.\n\nQuando o smartphone estiver resfriado, ele voltará ao normal."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Seu dispositivo está ficando quente perto da porta de carregamento. Desconecte qualquer carregador ou acessório USB que esteja conectado, mas tome cuidado, porque o cabo também pode estar quente."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver etapas de cuidado"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atalho à esquerda"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atalho à direita"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 3251eecb1b47..37918d4da69d 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Esquema"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo adicional"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 2f9a36e64f29..6ce4ccb37870 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string> <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo extra"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Alguns recursos ficam limitados enquanto o smartphone é resfriado.\nToque para saber mais"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Seu smartphone tentará se resfriar automaticamente. Você ainda poderá usá-lo, mas talvez ele fique mais lento.\n\nQuando o smartphone estiver resfriado, ele voltará ao normal."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Seu dispositivo está ficando quente perto da porta de carregamento. Desconecte qualquer carregador ou acessório USB que esteja conectado, mas tome cuidado, porque o cabo também pode estar quente."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver etapas de cuidado"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atalho à esquerda"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atalho à direita"</string> diff --git a/packages/SystemUI/res/values-ro-ldrtl/strings.xml b/packages/SystemUI/res/values-ro-ldrtl/strings.xml index e167b41c680c..a7cd33cd21da 100644 --- a/packages/SystemUI/res/values-ro-ldrtl/strings.xml +++ b/packages/SystemUI/res/values-ro-ldrtl/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="recents_quick_scrub_onboarding" msgid="2452671841151577157">"Trageți spre stânga pentru a comuta rapid între aplicații"</string> + <string name="recents_quick_scrub_onboarding" msgid="2452671841151577157">"Trage spre stânga pentru a comuta rapid între aplicații"</string> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 683774c18f8e..01f7e184ed01 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -20,53 +20,53 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4811759950673118541">"UI sistem"</string> - <string name="battery_low_title" msgid="5319680173344341779">"Activați Economisirea bateriei?"</string> + <string name="battery_low_title" msgid="5319680173344341779">"Activezi Economisirea bateriei?"</string> <string name="battery_low_description" msgid="3282977755476423966">"Mai aveți <xliff:g id="PERCENTAGE">%s</xliff:g> din baterie. Economisirea bateriei activează Tema întunecată, restricționează activitatea în fundal și amână notificările."</string> <string name="battery_low_intro" msgid="5148725009653088790">"Economisirea bateriei activează Tema întunecată, restricționează activitatea în fundal și amână notificările."</string> <string name="battery_low_percent_format" msgid="4276661262843170964">"Procent rămas din baterie: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="invalid_charger_title" msgid="938685362320735167">"Nu se poate realiza încărcarea prin USB"</string> - <string name="invalid_charger_text" msgid="2339310107232691577">"Folosiți încărcătorul livrat împreună cu dispozitivul"</string> - <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activați economisirea bateriei?"</string> + <string name="invalid_charger_text" msgid="2339310107232691577">"Folosește încărcătorul livrat împreună cu dispozitivul"</string> + <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activezi economisirea bateriei?"</string> <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Despre Economisirea bateriei"</string> - <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activați"</string> - <string name="battery_saver_start_action" msgid="8353766979886287140">"Activați"</string> + <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activează"</string> + <string name="battery_saver_start_action" msgid="8353766979886287140">"Activează"</string> <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nu, mulțumesc"</string> <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotire automată a ecranului"</string> - <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiteți <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> - <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiteți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string> - <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Permiteți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> - <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> + <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> + <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string> + <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> + <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> <string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"Permisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB. Dacă folosiți <xliff:g id="APPLICATION">%1$s</xliff:g> cu acest dispozitiv, acest lucru vă poate împiedica să auziți apeluri, notificări și alarme."</string> <string name="usb_audio_device_prompt" msgid="7944987408206252949">"Dacă folosiți <xliff:g id="APPLICATION">%1$s</xliff:g> cu acest dispozitiv, acest lucru vă poate împiedica să auziți apeluri, notificări și alarme."</string> - <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"Permiteți <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string> - <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> - <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> pentru a gestiona <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string> - <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string> - <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"Aplic. instal. nu funcț. cu acest acces. USB. Aflați despre acest accesoriu la <xliff:g id="URL">%1$s</xliff:g>"</string> + <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string> + <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> + <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> pentru a gestiona <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string> + <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string> + <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"Aplic. instal. nu funcț. cu acest acces. USB. Află despre acest accesoriu la <xliff:g id="URL">%1$s</xliff:g>"</string> <string name="title_usb_accessory" msgid="1236358027511638648">"Accesoriu USB"</string> - <string name="label_view" msgid="6815442985276363364">"Afișați"</string> - <string name="always_use_device" msgid="210535878779644679">"Deschideți întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string> - <string name="always_use_accessory" msgid="1977225429341838444">"Deschideți întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string> - <string name="usb_debugging_title" msgid="8274884945238642726">"Permiteți remedierea erorilor prin USB?"</string> + <string name="label_view" msgid="6815442985276363364">"Afișează"</string> + <string name="always_use_device" msgid="210535878779644679">"Deschide întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string> + <string name="always_use_accessory" msgid="1977225429341838444">"Deschide întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string> + <string name="usb_debugging_title" msgid="8274884945238642726">"Permiți remedierea erorilor prin USB?"</string> <string name="usb_debugging_message" msgid="5794616114463921773">"Amprenta din cheia RSA a computerului este:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> - <string name="usb_debugging_always" msgid="4003121804294739548">"Permiteți întotdeauna de pe acest computer"</string> - <string name="usb_debugging_allow" msgid="1722643858015321328">"Permiteți"</string> + <string name="usb_debugging_always" msgid="4003121804294739548">"Permite întotdeauna de pe acest computer"</string> + <string name="usb_debugging_allow" msgid="1722643858015321328">"Permite"</string> <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Remedierea erorilor prin USB nu este permisă"</string> - <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comutați la utilizatorul principal."</string> + <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comută la utilizatorul principal."</string> <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Schimbați limba de sistem la <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string> <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Alt dispozitiv solicită schimbarea limbii de sistem"</string> - <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Schimbați limba"</string> - <string name="hdmi_cec_set_menu_language_decline" msgid="7650721096558646011">"Păstrați limba actuală"</string> - <string name="wifi_debugging_title" msgid="7300007687492186076">"Permiteți remedierea erorilor wireless în această rețea?"</string> + <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Schimbă limba"</string> + <string name="hdmi_cec_set_menu_language_decline" msgid="7650721096558646011">"Păstrează limba actuală"</string> + <string name="wifi_debugging_title" msgid="7300007687492186076">"Permiți remedierea erorilor wireless în această rețea?"</string> <string name="wifi_debugging_message" msgid="5461204211731802995">"Numele rețelei (SSID)\n<xliff:g id="SSID_0">%1$s</xliff:g>\n\nAdresa Wi‑Fi (BSSID)\n<xliff:g id="BSSID_1">%2$s</xliff:g>"</string> - <string name="wifi_debugging_always" msgid="2968383799517975155">"Permiteți întotdeauna în această rețea"</string> - <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permiteți"</string> + <string name="wifi_debugging_always" msgid="2968383799517975155">"Permite întotdeauna în această rețea"</string> + <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permite"</string> <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Remedierea erorilor wireless nu este permisă"</string> <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor wireless. Pentru a folosi această funcție, comutați la utilizatorul principal."</string> <string name="usb_contaminant_title" msgid="894052515034594113">"Portul USB a fost dezactivat"</string> - <string name="usb_contaminant_message" msgid="7730476585174719805">"Pentru a vă proteja dispozitivul de lichide sau reziduuri, portul USB este dezactivat și nu va detecta niciun accesoriu.\n\nVeți primi o notificare când puteți folosi din nou portul USB."</string> + <string name="usb_contaminant_message" msgid="7730476585174719805">"Pentru a proteja dispozitivul de lichide sau reziduuri, portul USB este dezactivat și nu va detecta niciun accesoriu.\n\nVei primi o notificare când poți folosi din nou portul USB."</string> <string name="usb_port_enabled" msgid="531823867664717018">"Portul USB a fost activat pentru a detecta încărcătoarele și accesoriile"</string> - <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Activați USB"</string> + <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Activează USB"</string> <string name="learn_more" msgid="4690632085667273811">"Mai multe"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captură de ecran"</string> <string name="global_action_smart_lock_disabled" msgid="9097102067802412936">"Smart Lock dezactivat"</string> @@ -75,15 +75,15 @@ <string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string> <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblocați dispozitivul"</string> - <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încercați să faceți din nou o captură de ecran"</string> + <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string> <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string> - <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Crearea capturilor de ecran nu este permisă de aplicație sau de organizația dvs."</string> + <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Crearea capturilor de ecran nu e permisă de aplicație sau de organizația ta"</string> <string name="screenshot_blocked_by_admin" msgid="5486757604822795797">"Administratorul IT a blocat crearea capturilor de ecran"</string> - <string name="screenshot_edit_label" msgid="8754981973544133050">"Editați"</string> - <string name="screenshot_edit_description" msgid="3333092254706788906">"Editați captura de ecran"</string> - <string name="screenshot_share_description" msgid="2861628935812656612">"Trimiteți captura de ecran"</string> + <string name="screenshot_edit_label" msgid="8754981973544133050">"Editează"</string> + <string name="screenshot_edit_description" msgid="3333092254706788906">"Editează captura de ecran"</string> + <string name="screenshot_share_description" msgid="2861628935812656612">"Trimite captura de ecran"</string> <string name="screenshot_scroll_label" msgid="2930198809899329367">"Surprindeți mai mult"</string> - <string name="screenshot_dismiss_description" msgid="4702341245899508786">"Închideți captura de ecran"</string> + <string name="screenshot_dismiss_description" msgid="4702341245899508786">"Închide captura de ecran"</string> <string name="screenshot_preview_description" msgid="7606510140714080474">"Previzualizare a capturii de ecran"</string> <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Marginea de sus la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marginea de jos la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> @@ -102,50 +102,50 @@ <string name="screenrecord_start" msgid="330991441575775004">"Începeți"</string> <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Se înregistrează ecranul"</string> <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Se înregistrează ecranul și conținutul audio"</string> - <string name="screenrecord_taps_label" msgid="1595690528298857649">"Afișați atingerile de pe ecran"</string> - <string name="screenrecord_stop_label" msgid="72699670052087989">"Opriți"</string> - <string name="screenrecord_share_label" msgid="5025590804030086930">"Trimiteți"</string> + <string name="screenrecord_taps_label" msgid="1595690528298857649">"Afișează atingerile de pe ecran"</string> + <string name="screenrecord_stop_label" msgid="72699670052087989">"Oprește"</string> + <string name="screenrecord_share_label" msgid="5025590804030086930">"Trimite"</string> <string name="screenrecord_save_title" msgid="1886652605520893850">"Înregistrarea ecranului a fost salvată"</string> - <string name="screenrecord_save_text" msgid="3008973099800840163">"Atingeți pentru a afișa"</string> + <string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string> <string name="screenrecord_delete_error" msgid="2870506119743013588">"Eroare la ștergerea înregistrării ecranului"</string> <string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string> <string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string> <string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string> <string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string> <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Accesibilitate"</string> - <string name="accessibility_rotate_button" msgid="1238584767612362586">"Rotiți ecranul"</string> + <string name="accessibility_rotate_button" msgid="1238584767612362586">"Rotește ecranul"</string> <string name="accessibility_recent" msgid="901641734769533575">"Recente"</string> <string name="accessibility_camera_button" msgid="2938898391716647247">"Cameră foto"</string> <string name="accessibility_phone_button" msgid="4256353121703100427">"Telefon"</string> <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistent vocal"</string> <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofel"</string> <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner de coduri QR"</string> - <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblocați"</string> + <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblochează"</string> <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispozitiv blocat"</string> <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanarea chipului"</string> - <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimiteți"</string> - <string name="cancel" msgid="1089011503403416730">"Anulați"</string> - <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Confirmați"</string> - <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Încercați din nou"</string> - <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Atingeți pentru a anula autentificarea"</string> - <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Încercați din nou"</string> + <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimite"</string> + <string name="cancel" msgid="1089011503403416730">"Anulează"</string> + <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Confirmă"</string> + <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Încearcă din nou"</string> + <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Atinge pentru a anula autentificarea"</string> + <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Încearcă din nou"</string> <string name="biometric_dialog_face_icon_description_authenticating" msgid="3401633342366146535">"Se caută chipul"</string> <string name="biometric_dialog_face_icon_description_authenticated" msgid="2242167416140740920">"Chip autentificat"</string> <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"Confirmat"</string> - <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Atingeți Confirmați pentru a finaliza"</string> - <string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"S-a deblocat cu ajutorul feței. Apăsați pictograma de deblocare pentru a continua"</string> - <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"S-a deblocat cu ajutorul feței. Apăsați pentru a continua."</string> - <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Chipul a fost recunoscut. Apăsați pentru a continua."</string> + <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Atinge Confirm pentru a finaliza"</string> + <string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"S-a deblocat cu ajutorul feței. Apasă pictograma de deblocare pentru a continua"</string> + <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"S-a deblocat cu ajutorul feței. Apasă pentru a continua."</string> + <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Chipul a fost recunoscut. Apasă pentru a continua."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Chip recunoscut. Apăsați pictograma de deblocare să continuați."</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Autentificat"</string> - <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Folosiți PIN-ul"</string> - <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Folosiți modelul"</string> - <string name="biometric_dialog_use_password" msgid="3445033859393474779">"Folosiți parola"</string> + <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Folosește PIN-ul"</string> + <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Folosește modelul"</string> + <string name="biometric_dialog_use_password" msgid="3445033859393474779">"Folosește parola"</string> <string name="biometric_dialog_wrong_pin" msgid="1878539073972762803">"PIN greșit"</string> <string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"Model greșit"</string> <string name="biometric_dialog_wrong_password" msgid="69477929306843790">"Parolă greșită"</string> - <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"Prea multe încercări incorecte.\nÎncercați din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string> - <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"Încercați din nou. Încercarea <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> din <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>."</string> + <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"Prea multe încercări incorecte.\nÎncearcă din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string> + <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"Încearcă din nou. Încercarea <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> din <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>."</string> <string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"Datele dvs. vor fi șterse"</string> <string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"Dacă la următoarea încercare introduceți un model incorect, datele de pe acest dispozitiv vor fi șterse."</string> <string name="biometric_dialog_last_pin_attempt_before_wipe_device" msgid="9151756675698215723">"Dacă la următoarea încercare introduceți un cod PIN incorect, datele de pe acest dispozitiv vor fi șterse."</string> @@ -156,9 +156,9 @@ <string name="biometric_dialog_last_pattern_attempt_before_wipe_profile" msgid="6045224069529284686">"Dacă la următoarea încercare introduceți un model incorect, profilul de serviciu și datele sale vor fi șterse."</string> <string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"Dacă la următoarea încercare introduceți un cod PIN incorect, profilul de serviciu și datele sale vor fi șterse."</string> <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"Dacă la următoarea încercare introduceți o parolă incorectă, profilul de serviciu și datele sale vor fi șterse."</string> - <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atingeți senzorul de amprente"</string> + <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atinge senzorul de amprente"</string> <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Pictograma amprentă"</string> - <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosiți amprenta."</string> + <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosește amprenta."</string> <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) --> <skip /> <string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string> @@ -175,7 +175,7 @@ <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterie: <xliff:g id="NUMBER">%d</xliff:g> la sută."</string> <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Procentul rămas din baterie este <xliff:g id="PERCENTAGE">%1$s</xliff:g>. În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria se încarcă, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> la sută."</string> - <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vedeți toate notificările"</string> + <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vezi toate notificările"</string> <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter activat."</string> <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrare sonerie."</string> <string name="accessibility_ringer_silent" msgid="8994620163934249882">"Sonerie silențioasă."</string> @@ -185,7 +185,7 @@ <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Setări rapide."</string> <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Ecranul de blocare."</string> <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Ecran de blocare pentru serviciu"</string> - <string name="accessibility_desc_close" msgid="8293708213442107755">"Închideți"</string> + <string name="accessibility_desc_close" msgid="8293708213442107755">"Închide"</string> <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"niciun sunet"</string> <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"numai alarme"</string> <string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"Nu deranja."</string> @@ -198,11 +198,11 @@ <string name="accessibility_brightness" msgid="5391187016177823721">"Luminozitatea ecranului"</string> <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Datele mobile sunt întrerupte"</string> <string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Conexiunea de date este întreruptă"</string> - <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"A fost atinsă limita de date setată. Datele mobile nu mai sunt folosite.\n\nDacă reluați, este posibil să se aplice taxe pentru utilizarea datelor."</string> - <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Reluați"</string> + <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"A fost atinsă limita de date setată. Datele mobile nu mai sunt folosite.\n\nDacă reiei, se pot aplica taxe pentru utilizarea datelor."</string> + <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Reia"</string> <string name="accessibility_location_active" msgid="2845747916764660369">"Solicitări locație active"</string> <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Dezactivarea senzorilor este activă"</string> - <string name="accessibility_clear_all" msgid="970525598287244592">"Ștergeți toate notificările."</string> + <string name="accessibility_clear_all" msgid="970525598287244592">"Șterge toate notificările."</string> <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string> <string name="notification_group_overflow_description" msgid="7176322877233433278">"{count,plural, =1{Încă # notificare în grup.}few{Încă # notificări în grup.}other{Încă # de notificări în grup.}}"</string> <string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"Ecranul este blocat în orientarea de tip peisaj."</string> @@ -245,7 +245,7 @@ <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string> - <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închideți"</string> + <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închide"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectat"</string> <string name="quick_settings_connected_battery_level" msgid="1322075669498906959">"Conectat, bateria la <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="quick_settings_connecting" msgid="2381969772953268809">"Se conectează..."</string> @@ -281,7 +281,7 @@ <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Serviciul NFC este activat"</string> <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Înregistrarea ecranului"</string> <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Începeți"</string> - <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Opriți"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Oprește"</string> <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Modul cu o mână"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblocați microfonul dispozitivului?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblocați camera dispozitivului?"</string> @@ -299,21 +299,21 @@ <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cameră foto disponibilă"</string> <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfon și cameră disponibile"</string> <string name="media_seamless_other_device" msgid="4654849800789196737">"Alt dispozitiv"</string> - <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comutați secțiunea Recente"</string> - <string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de dvs. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string> - <string name="zen_alarms_introduction" msgid="3987266042682300470">"Se vor anunța prin sunete și vibrații numai alarmele. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string> - <string name="zen_priority_customize_button" msgid="4119213187257195047">"Personalizați"</string> - <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor. Totuși, veți putea iniția apeluri."</string> + <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comută secțiunea Recente"</string> + <string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de tine. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string> + <string name="zen_alarms_introduction" msgid="3987266042682300470">"Se vor anunța prin sunete și vibrații numai alarmele. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string> + <string name="zen_priority_customize_button" msgid="4119213187257195047">"Personalizează"</string> + <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor. Totuși, vei putea iniția apeluri."</string> <string name="zen_silence_introduction" msgid="6117517737057344014">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor."</string> - <string name="notification_tap_again" msgid="4477318164947497249">"Atingeți din nou pentru a deschide"</string> - <string name="tap_again" msgid="1315420114387908655">"Atingeți din nou"</string> - <string name="keyguard_unlock" msgid="8031975796351361601">"Glisați în sus pentru a deschide"</string> - <string name="keyguard_unlock_press" msgid="9140109453735019209">"Apăsați pictograma de deblocare pentru a deschide"</string> + <string name="notification_tap_again" msgid="4477318164947497249">"Atinge din nou pentru a deschide"</string> + <string name="tap_again" msgid="1315420114387908655">"Atinge din nou"</string> + <string name="keyguard_unlock" msgid="8031975796351361601">"Glisează în sus pentru a deschide"</string> + <string name="keyguard_unlock_press" msgid="9140109453735019209">"Apasă pictograma de deblocare pentru a deschide"</string> <string name="keyguard_face_successful_unlock_swipe" msgid="6180997591385846073">"S-a deblocat folosind fața. Glisați în sus și deschideți."</string> - <string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"S-a deblocat cu ajutorul feței. Apăsați pictograma de deblocare pentru a deschide"</string> - <string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"S-a deblocat cu ajutorul feței. Apăsați pentru a deschide."</string> - <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Chipul a fost recunoscut. Apăsați pentru a deschide."</string> - <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Chip recunoscut. Apăsați pictograma de deblocare pentru a deschide"</string> + <string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"S-a deblocat cu ajutorul feței. Apasă pictograma de deblocare pentru a deschide"</string> + <string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"S-a deblocat cu ajutorul feței. Apasă pentru a deschide."</string> + <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Chipul a fost recunoscut. Apasă pentru a deschide."</string> + <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Chip recunoscut. Apasă pictograma de deblocare pentru a deschide"</string> <string name="keyguard_face_successful_unlock" msgid="4203999851465708287">"S-a deblocat folosind fața"</string> <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Chipul a fost recunoscut"</string> <string-array name="udfps_accessibility_touch_hints"> @@ -322,14 +322,14 @@ <item msgid="4844142668312841831">"Deplasați spre dreapta"</item> <item msgid="5640521437931460125">"Deplasați în sus"</item> </string-array> - <string name="keyguard_retry" msgid="886802522584053523">"Glisați pentru a încerca din nou"</string> - <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Deblocați pentru a folosi NFC"</string> + <string name="keyguard_retry" msgid="886802522584053523">"Glisează pentru a încerca din nou"</string> + <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Deblochează pentru a folosi NFC"</string> <string name="do_disclosure_generic" msgid="4896482821974707167">"Dispozitivul aparține organizației dvs."</string> <string name="do_disclosure_with_name" msgid="2091641464065004091">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string> <string name="do_financed_disclosure_with_name" msgid="6723004643314467864">"Acest dispozitiv este oferit de <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string> - <string name="phone_hint" msgid="6682125338461375925">"Glisați dinspre telefon"</string> - <string name="voice_hint" msgid="7476017460191291417">"Glisați dinspre pictogramă pentru asistentul vocal"</string> - <string name="camera_hint" msgid="4519495795000658637">"Glisați pentru a fotografia"</string> + <string name="phone_hint" msgid="6682125338461375925">"Glisează dinspre telefon"</string> + <string name="voice_hint" msgid="7476017460191291417">"Glisează dinspre pictogramă pentru asistentul vocal"</string> + <string name="camera_hint" msgid="4519495795000658637">"Glisează pentru a fotografia"</string> <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"Liniște absolută. Se va opri sunetul și pentru cititoarele de ecran."</string> <string name="interruption_level_none" msgid="219484038314193379">"Niciun sunet"</string> <string name="interruption_level_priority" msgid="661294280016622209">"Numai cu prioritate"</string> @@ -342,35 +342,35 @@ <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string> <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string> - <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Comutați între utilizatori"</string> + <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string> - <string name="guest_wipe_session_title" msgid="7147965814683990944">"Bine ați revenit în sesiunea pentru invitați!"</string> - <string name="guest_wipe_session_message" msgid="3393823610257065457">"Vreți să continuați sesiunea?"</string> - <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Începeți din nou"</string> - <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Da, continuați"</string> + <string name="guest_wipe_session_title" msgid="7147965814683990944">"Bine ai revenit în sesiunea pentru invitați!"</string> + <string name="guest_wipe_session_message" msgid="3393823610257065457">"Continui sesiunea?"</string> + <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Începe din nou"</string> + <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Da, continuă"</string> <string name="guest_notification_app_name" msgid="2110425506754205509">"Modul pentru invitați"</string> - <string name="guest_notification_session_active" msgid="5567273684713471450">"Folosiți modul pentru invitați"</string> + <string name="guest_notification_session_active" msgid="5567273684713471450">"Folosește modul pentru invitați"</string> <string name="user_add_user_message_guest_remove" msgid="5589286604543355007">\n\n"Dacă adăugați un utilizator nou, veți ieși din modul pentru invitați și se vor șterge toate aplicațiile și datele din sesiunea pentru invitați actuală."</string> - <string name="user_limit_reached_title" msgid="2429229448830346057">"Ați atins limita de utilizatori"</string> + <string name="user_limit_reached_title" msgid="2429229448830346057">"Ai atins limita de utilizatori"</string> <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{Se poate crea doar un utilizator.}few{Puteți adăuga până la # utilizatori.}other{Puteți adăuga până la # de utilizatori.}}"</string> - <string name="user_remove_user_title" msgid="9124124694835811874">"Eliminați utilizatorul?"</string> + <string name="user_remove_user_title" msgid="9124124694835811874">"Elimini utilizatorul?"</string> <string name="user_remove_user_message" msgid="6702834122128031833">"Toate aplicațiile și datele acestui utilizator vor fi șterse."</string> - <string name="user_remove_user_remove" msgid="8387386066949061256">"Eliminați"</string> + <string name="user_remove_user_remove" msgid="8387386066949061256">"Elimină"</string> <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string> <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începeți să înregistrați sau să proiectați?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"Începeți să înregistrați sau să proiectați cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string> - <string name="clear_all_notifications_text" msgid="348312370303046130">"Ștergeți toate notificările"</string> - <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionați"</string> + <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string> + <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string> <string name="notification_section_header_incoming" msgid="850925217908095197">"Noi"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silențioase"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificări"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversații"</string> - <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ștergeți toate notificările silențioase"</string> + <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Șterge toate notificările silențioase"</string> <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificări întrerupte prin „Nu deranja”"</string> - <string name="media_projection_action_text" msgid="3634906766918186440">"Începeți acum"</string> + <string name="media_projection_action_text" msgid="3634906766918186440">"Începe acum"</string> <string name="empty_shade_text" msgid="8935967157319717412">"Nicio notificare"</string> <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Dispozitivul este gestionat de unul dintre părinți"</string> <string name="quick_settings_disclosure_management_monitoring" msgid="8231336875820702180">"Organizația dvs. deține acest dispozitiv și poate monitoriza traficul de rețea"</string> @@ -382,8 +382,8 @@ <string name="quick_settings_disclosure_named_management" msgid="3476472755775165827">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>"</string> <string name="quick_settings_disclosure_management_vpns" msgid="929181757984262902">"Acest dispozitiv aparține organizației dvs. și este conectat la internet prin rețele VPN."</string> <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> și este conectat la internet prin rețele VPN."</string> - <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"Este posibil ca organizația dvs. să monitorizeze traficul de rețea în profilul dvs. de serviciu"</string> - <string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"Este posibil ca <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> să monitorizeze traficul de rețea din profilul dvs. de serviciu"</string> + <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"E posibil ca organizația ta să monitorizeze traficul de rețea în profilul de serviciu"</string> + <string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"E posibil ca <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> să monitorizeze traficul de rețea din profilul tău de serviciu"</string> <string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"Adminul IT poate vedea profilul de serviciu"</string> <string name="quick_settings_disclosure_monitoring" msgid="8548019955631378680">"Este posibil ca rețeaua să fie monitorizată"</string> <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"Acest dispozitiv este conectat la internet prin rețele VPN."</string> @@ -395,40 +395,40 @@ <string name="monitoring_subtitle_vpn" msgid="800485258004629079">"VPN"</string> <string name="monitoring_subtitle_network_logging" msgid="2444199331891219596">"Înregistrare în jurnal pentru rețea"</string> <string name="monitoring_subtitle_ca_certificate" msgid="8588092029755175800">"Certificate CA"</string> - <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Afișați politicile"</string> - <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Vedeți opțiunile"</string> + <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Afișează politicile"</string> + <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Vezi opțiunile"</string> <string name="monitoring_description_named_management" msgid="505833016545056036">"Dispozitivul aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nAdministratorul dvs. IT poate să monitorizeze și să gestioneze setările, accesul la nivelul companiei, aplicațiile, datele asociate dispozitivului și informațiile despre locația dispozitivului.\n\nPentru mai multe informații, contactați administratorul IT."</string> <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"Este posibil ca <xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> să acceseze date asociate dispozitivului, să gestioneze aplicații și să modifice setările acestuia.\n\nDacă aveți întrebări, luați legătura cu <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string> <string name="monitoring_description_management" msgid="4308879039175729014">"Dispozitivul aparține organizației dvs.\n\nAdministratorul dvs. IT poate să monitorizeze și să gestioneze setările, accesul la nivelul companiei, aplicațiile, datele asociate dispozitivului și informațiile despre locația dispozitivului.\n\nPentru mai multe informații, contactați administratorul IT."</string> - <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Organizația dvs. a instalat un certificat CA pe acest dispozitiv. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string> - <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Organizația dvs. a instalat un certificat CA în profilul dvs. de serviciu. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string> - <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Pe acest dispozitiv este instalat un certificat CA. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string> - <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Administratorul dvs. a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul de pe dispozitivul dvs."</string> + <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Organizația ta a instalat un certificat CA pe acest dispozitiv. Traficul de rețea securizat poate fi monitorizat sau modificat."</string> + <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Organizația ta a instalat un certificat CA în profilul tău de serviciu. Traficul de rețea securizat poate fi monitorizat sau modificat."</string> + <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Pe acest dispozitiv este instalat un certificat CA. Traficul de rețea securizat poate fi monitorizat sau modificat."</string> + <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Administratorul tău a activat înregistrarea în jurnal pentru rețea, funcție care monitorizează traficul de pe dispozitivul tău."</string> <string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"Administratorul a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul în profilul dvs. de serviciu, dar nu și în profilul personal."</string> <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"Acest dispozitiv este conectat la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT."</string> <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Acest dispozitiv este conectat la internet prin aplicațiile <xliff:g id="VPN_APP_0">%1$s</xliff:g> și <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT."</string> <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Aplicațiile dvs. pentru lucru sunt conectate la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea cu aplicațiile pentru lucru, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT și pentru furnizorul de servicii VPN."</string> <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Aplicațiile dvs. personale sunt conectate la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru furnizorul de servicii VPN."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string> - <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Deschideți Setări VPN"</string> + <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Deschide Setări VPN"</string> <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Dispozitivul este gestionat de unul dintre părinți. Părintele poate să vadă și să gestioneze informații cum ar fi aplicațiile pe care le folosești, locația ta și durata de folosire a dispozitivului."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Deblocat de TrustAgent"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> <string name="accessibility_volume_settings" msgid="1458961116951564784">"Setări de sunet"</string> - <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Adăugați subtitrări automate la fișierele media"</string> + <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Adaugă subtitrări automate la fișierele media"</string> <string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"Sfat pentru subtitrări"</string> <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Suprapunere pe subtitrări"</string> - <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"activați"</string> - <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"dezactivați"</string> + <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"activează"</string> + <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"dezactivează"</string> <string name="sound_settings" msgid="8874581353127418308">"Sunete și vibrații"</string> <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Setări"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"Aplicația este fixată"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Recente pentru a anula fixarea."</string> - <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Acasă pentru a anula fixarea."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișată până anulați fixarea. Glisați în sus și țineți apăsat pentru a anula fixarea."</string> - <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Recente pentru a anula fixarea."</string> - <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Acasă pentru a anula fixarea."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunile Înapoi și Recente pentru a anula fixarea."</string> + <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunile Înapoi și Acasă pentru a anula fixarea."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișată până anulați fixarea. Glisează în sus și ține apăsat pentru a anula fixarea."</string> + <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunea Recente pentru a anula fixarea."</string> + <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunea Acasă pentru a anula fixarea."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Pot fi accesate date cu caracter personal (cum ar fi agenda și conținutul e-mailurilor)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Aplicațiile fixate pot deschide alte aplicații."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Pentru a anula fixarea acestei aplicații, atingeți lung butoanele Înapoi și Recente"</string> @@ -449,57 +449,57 @@ <string name="stream_accessibility" msgid="3873610336741987152">"Accesibilitate"</string> <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonerie"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrații"</string> - <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blocați"</string> - <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atingeți pentru a activa sunetul."</string> - <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atingeți pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> - <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atingeți pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> - <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atingeți pentru a seta pe vibrații."</string> - <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atingeți pentru a dezactiva sunetul."</string> - <string name="volume_ringer_change" msgid="3574969197796055532">"Atingeți pentru a schimba modul soneriei"</string> - <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivați sunetul"</string> - <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activați sunetul"</string> + <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blochează"</string> + <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atinge pentru a activa sunetul."</string> + <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atinge pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> + <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atinge pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> + <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atinge pentru a seta pe vibrații."</string> + <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atinge pentru a dezactiva sunetul."</string> + <string name="volume_ringer_change" msgid="3574969197796055532">"Atinge pentru a schimba modul soneriei"</string> + <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivează sunetul"</string> + <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activează sunetul"</string> <string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"vibrații"</string> <string name="volume_dialog_title" msgid="6502703403483577940">"Comenzi de volum pentru %s"</string> <string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"Apelurile și notificările vor suna (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Bară de stare"</string> <string name="demo_mode" msgid="263484519766901593">"Mod demonstrativ pentru IU sistem"</string> - <string name="enable_demo_mode" msgid="3180345364745966431">"Activați modul demonstrativ"</string> - <string name="show_demo_mode" msgid="3677956462273059726">"Afișați modul demonstrativ"</string> + <string name="enable_demo_mode" msgid="3180345364745966431">"Activează modul demonstrativ"</string> + <string name="show_demo_mode" msgid="3677956462273059726">"Afișează modul demonstrativ"</string> <string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string> <string name="status_bar_alarm" msgid="87160847643623352">"Alarmă"</string> <string name="wallet_title" msgid="5369767670735827105">"Portofel"</string> <string name="wallet_empty_state_label" msgid="7776761245237530394">"Configurați pentru a face achiziții mai rapide și mai sigure cu telefonul dvs."</string> - <string name="wallet_app_button_label" msgid="7123784239111190992">"Afișați-le pe toate"</string> - <string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Atingeți pentru a deschide"</string> + <string name="wallet_app_button_label" msgid="7123784239111190992">"Afișează-le pe toate"</string> + <string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Atinge pentru a deschide"</string> <string name="wallet_secondary_label_updating" msgid="5726130686114928551">"Se actualizează"</string> - <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblocați pentru a folosi"</string> - <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încercați din nou mai târziu"</string> + <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblochează pentru a folosi"</string> + <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încearcă din nou mai târziu"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string> <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanați codul QR"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string> - <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu veți auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string> + <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu vei auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"la <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"Hotspot"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil de serviciu"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Distractiv pentru unii, dar nu pentru toată lumea"</string> - <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vă oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuați cu prudență."</string> - <string name="tuner_persistent_warning" msgid="230466285569307806">"Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuați cu prudență."</string> + <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string> + <string name="tuner_persistent_warning" msgid="230466285569307806">"Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string> <string name="got_it" msgid="477119182261892069">"Am înțeles"</string> <string name="tuner_toast" msgid="3812684836514766951">"Felicitări! System UI Tuner a fost adăugat în Setări"</string> - <string name="remove_from_settings" msgid="633775561782209994">"Eliminați din Setări"</string> - <string name="remove_from_settings_prompt" msgid="551565437265615426">"Eliminați System UI Tuner din Setări și încetați utilizarea tuturor funcțiilor sale?"</string> - <string name="enable_bluetooth_title" msgid="866883307336662596">"Activați Bluetooth?"</string> - <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string> - <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activați"</string> + <string name="remove_from_settings" msgid="633775561782209994">"Elimină din Setări"</string> + <string name="remove_from_settings_prompt" msgid="551565437265615426">"Elimini System UI Tuner din Setări și încetezi utilizarea tuturor funcțiilor sale?"</string> + <string name="enable_bluetooth_title" msgid="866883307336662596">"Activezi Bluetooth?"</string> + <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activezi Bluetooth."</string> + <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activează"</string> <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Comenzi de gestionare a notificărilor"</string> <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activată – În funcție de chip"</string> - <string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, puteți să setați un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string> + <string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, poți seta un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string> <string name="inline_done_button" msgid="6043094985588909584">"Gata"</string> - <string name="inline_ok_button" msgid="603075490581280343">"Aplicați"</string> - <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Dezactivați notificările"</string> + <string name="inline_ok_button" msgid="603075490581280343">"Aplică"</string> + <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Dezactivează notificările"</string> <string name="notification_silence_title" msgid="8608090968400832335">"Silențios"</string> <string name="notification_alert_title" msgid="3656229781017543655">"Prestabilite"</string> <string name="notification_automatic_title" msgid="3745465364578762652">"Automat"</string> @@ -523,23 +523,23 @@ <string name="notification_multichannel_desc" msgid="7414593090056236179">"Acest grup de notificări nu poate fi configurat aici"</string> <string name="notification_delegate_header" msgid="1264510071031479920">"Notificare prin proxy"</string> <string name="notification_channel_dialog_title" msgid="6856514143093200019">"Toate notificările din <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> - <string name="see_more_title" msgid="7409317011708185729">"Vedeți mai multe"</string> + <string name="see_more_title" msgid="7409317011708185729">"Vezi mai multe"</string> <string name="feedback_alerted" msgid="5192459808484271208">"Notificarea a fost <b>promovată automat la Prestabilită</b> de sistem."</string> <string name="feedback_silenced" msgid="9116540317466126457">"Notificarea a fost <b>setată automat ca Silențioasă</b> de sistem."</string> <string name="feedback_promoted" msgid="2125562787759780807">"Notificarea a fost <b>clasificată automat mai sus</b> în umbră."</string> <string name="feedback_demoted" msgid="951884763467110604">"Notificarea a fost <b>clasificată automat mai jos</b> în umbră."</string> - <string name="feedback_prompt" msgid="3656728972307896379">"Trimiteți feedback dezvoltatorului. Este corect?"</string> + <string name="feedback_prompt" msgid="3656728972307896379">"Trimite feedback dezvoltatorului. Este corect?"</string> <string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"Opțiunile privind notificările pentru <xliff:g id="APP_NAME">%1$s</xliff:g> sunt afișate"</string> <string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"Opțiunile privind notificările pentru <xliff:g id="APP_NAME">%1$s</xliff:g> nu sunt afișate"</string> <string name="notification_more_settings" msgid="4936228656989201793">"Mai multe setări"</string> - <string name="notification_app_settings" msgid="8963648463858039377">"Personalizați"</string> - <string name="notification_conversation_bubble" msgid="2242180995373949022">"Afișați balonul"</string> - <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Eliminați baloanele"</string> + <string name="notification_app_settings" msgid="8963648463858039377">"Personalizează"</string> + <string name="notification_conversation_bubble" msgid="2242180995373949022">"Afișează balonul"</string> + <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Elimină baloanele"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"comenzile notificării"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opțiuni de amânare a notificării"</string> <string name="notification_menu_snooze_action" msgid="5415729610393475019">"Reamintește-mi"</string> - <string name="snooze_undo" msgid="2738844148845992103">"Anulați"</string> + <string name="snooze_undo" msgid="2738844148845992103">"Anulează"</string> <string name="snoozed_for_time" msgid="7586689374860469469">"Amânată <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string> <string name="snoozeHourOptions" msgid="2332819756222425558">"{count,plural, =1{# oră}=2{# ore}few{# ore}other{# de ore}}"</string> <string name="snoozeMinuteOptions" msgid="2222082405822030979">"{count,plural, =1{# minut}few{# minute}other{# de minute}}"</string> @@ -556,28 +556,28 @@ <string name="keyboard_key_space" msgid="6980847564173394012">"Spațiu"</string> <string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string> <string name="keyboard_key_backspace" msgid="4095278312039628074">"Backspace"</string> - <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Redați/Întrerupeți"</string> - <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Opriți"</string> + <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Redă/Întrerupe"</string> + <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Oprește"</string> <string name="keyboard_key_media_next" msgid="8502476691227914952">"Înainte"</string> <string name="keyboard_key_media_previous" msgid="5637875709190955351">"Înapoi"</string> - <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Derulați înapoi"</string> - <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Derulați rapid înainte"</string> + <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Derulează înapoi"</string> + <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Derulează rapid înainte"</string> <string name="keyboard_key_page_up" msgid="173914303254199845">"O pagină mai sus"</string> <string name="keyboard_key_page_down" msgid="9035902490071829731">"O pagină mai jos"</string> - <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Ștergeți"</string> + <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Șterge"</string> <string name="keyboard_key_move_home" msgid="3496502501803911971">"La început"</string> <string name="keyboard_key_move_end" msgid="99190401463834854">"La final"</string> - <string name="keyboard_key_insert" msgid="4621692715704410493">"Inserați"</string> + <string name="keyboard_key_insert" msgid="4621692715704410493">"Inserează"</string> <string name="keyboard_key_num_lock" msgid="7209960042043090548">"Num Lock"</string> <string name="keyboard_key_numpad_template" msgid="7316338238459991821">"Tasta numerică <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Eliminați atașamentul"</string> + <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Elimină atașamentul"</string> <string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"Sistem"</string> <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Ecran de pornire"</string> <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recente"</string> <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Înapoi"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificări"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Comenzi rapide de la tastatură"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Schimbați aspectul tastaturii"</string> + <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Schimbă aspectul tastaturii"</string> <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Aplicații"</string> <string name="keyboard_shortcut_group_applications_assist" msgid="771606231466098742">"Asistent"</string> <string name="keyboard_shortcut_group_applications_browser" msgid="2776211137869809251">"Browser"</string> @@ -590,16 +590,15 @@ <string name="volume_dnd_silent" msgid="4154597281458298093">"Comandă rapidă din butoanele de volum"</string> <string name="battery" msgid="769686279459897127">"Baterie"</string> <string name="headset" msgid="4485892374984466437">"Set căști-microfon"</string> - <string name="accessibility_long_click_tile" msgid="210472753156768705">"Deschideți setările"</string> + <string name="accessibility_long_click_tile" msgid="210472753156768705">"Deschide setările"</string> <string name="accessibility_status_bar_headphones" msgid="1304082414912647414">"Căștile sunt conectate"</string> <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Setul căști-microfon este conectat"</string> <string name="data_saver" msgid="3484013368530820763">"Economizor de date"</string> <string name="accessibility_data_saver_on" msgid="5394743820189757731">"Economizorul de date este activat"</string> <string name="switch_bar_on" msgid="1770868129120096114">"Activat"</string> - <string name="switch_bar_off" msgid="5669805115416379556">"Dezactivați"</string> + <string name="switch_bar_off" msgid="5669805115416379556">"Dezactivează"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Indisponibil"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"a afla mai multe"</string> <string name="nav_bar" msgid="4642708685386136807">"Bară de navigare"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Aspect"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tip de buton din extrema stângă"</string> @@ -607,7 +606,7 @@ <string-array name="nav_bar_buttons"> <item msgid="2681220472659720036">"Clipboard"</item> <item msgid="4795049793625565683">"Cod de tastă"</item> - <item msgid="80697951177515644">"Confirmați rotirea, comutator de la tastatură"</item> + <item msgid="80697951177515644">"Confirmă rotirea, comutator de la tastatură"</item> <item msgid="7626977989589303588">"Niciunul"</item> </string-array> <string-array name="nav_bar_layouts"> @@ -616,19 +615,19 @@ <item msgid="7453955063378349599">"Înclinat spre stânga"</item> <item msgid="5874146774389433072">"Înclinat spre dreapta"</item> </string-array> - <string name="save" msgid="3392754183673848006">"Salvați"</string> - <string name="reset" msgid="8715144064608810383">"Resetați"</string> + <string name="save" msgid="3392754183673848006">"Salvează"</string> + <string name="reset" msgid="8715144064608810383">"Resetează"</string> <string name="clipboard" msgid="8517342737534284617">"Clipboard"</string> <string name="accessibility_key" msgid="3471162841552818281">"Buton personalizat pentru navigare"</string> <string name="left_keycode" msgid="8211040899126637342">"Codul de taste din stânga"</string> <string name="right_keycode" msgid="2480715509844798438">"Codul de taste din dreapta"</string> <string name="left_icon" msgid="5036278531966897006">"Pictograma din stânga"</string> <string name="right_icon" msgid="1103955040645237425">"Pictograma din dreapta"</string> - <string name="drag_to_add_tiles" msgid="8933270127508303672">"Țineți apăsat și trageți pentru a adăuga piese"</string> - <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Țineți apăsat și trageți pentru a rearanja piesele"</string> - <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Trageți aici pentru a elimina"</string> - <string name="drag_to_remove_disabled" msgid="933046987838658850">"Aveți nevoie de cel puțin <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> carduri"</string> - <string name="qs_edit" msgid="5583565172803472437">"Editați"</string> + <string name="drag_to_add_tiles" msgid="8933270127508303672">"Ține apăsat și trage pentru a adăuga carduri"</string> + <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Ține apăsat și trage pentru a rearanja cardurile"</string> + <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Trage aici pentru a elimina"</string> + <string name="drag_to_remove_disabled" msgid="933046987838658850">"Ai nevoie de cel puțin <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> carduri"</string> + <string name="qs_edit" msgid="5583565172803472437">"Editează"</string> <string name="tuner_time" msgid="2450785840990529997">"Oră"</string> <string-array name="clock_options"> <item msgid="3986445361435142273">"Afișează orele, minutele și secundele"</item> @@ -640,49 +639,47 @@ <item msgid="3805744470661798712">"Afișează procentajul când se încarcă (prestabilit)"</item> <item msgid="8619482474544321778">"Nu afișa această pictogramă"</item> </string-array> - <string name="tuner_low_priority" msgid="8412666814123009820">"Afișați pictogramele de notificare cu prioritate redusă"</string> + <string name="tuner_low_priority" msgid="8412666814123009820">"Afișează pictogramele de notificare cu prioritate redusă"</string> <string name="other" msgid="429768510980739978">"Altele"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"eliminați cardul"</string> <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adăugați cardul la sfârșit"</string> - <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mutați cardul"</string> - <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adăugați un card"</string> - <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mutați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> - <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adăugați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mută cardul"</string> + <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adaugă un card"</string> + <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mută pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editorul pentru setări rapide."</string> <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificare <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string> - <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Deschideți setările."</string> - <string name="accessibility_quick_settings_expand" msgid="2609275052412521467">"Deschideți setările rapide."</string> - <string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Închideți setările rapide."</string> + <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Deschide setările."</string> + <string name="accessibility_quick_settings_expand" msgid="2609275052412521467">"Deschide setările rapide."</string> + <string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Închide setările rapide."</string> <string name="accessibility_quick_settings_user" msgid="505821942882668619">"Conectat(ă) ca <xliff:g id="ID_1">%s</xliff:g>"</string> <string name="accessibility_quick_settings_choose_user_action" msgid="4554388498186576087">"alege utilizatorul"</string> <string name="data_connection_no_internet" msgid="691058178914184544">"Fără conexiune la internet"</string> - <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Deschideți setările <xliff:g id="ID_1">%s</xliff:g>."</string> - <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Editați ordinea setărilor."</string> + <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Deschide setările <xliff:g id="ID_1">%s</xliff:g>."</string> + <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Editează ordinea setărilor."</string> <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meniul de pornire"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> din <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Ecran de blocare"</string> <string name="thermal_shutdown_title" msgid="2702966892682930264">"Telefonul s-a oprit din cauza încălzirii"</string> - <string name="thermal_shutdown_message" msgid="6142269839066172984">"Acum telefonul funcționează normal.\nAtingeți pentru mai multe informații"</string> - <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonul se încălzise prea mult și s-a oprit pentru a se răci. Acum telefonul funcționează normal.\n\nTelefonul s-ar putea încălzi prea mult dacă:\n • folosiți aplicații care consumă multe resurse (de ex., jocuri, aplicații video/de navigare);\n • descărcați/încărcați fișiere mari;\n • folosiți telefonul la temperaturi ridicate."</string> - <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vedeți pașii pentru îngrijire"</string> + <string name="thermal_shutdown_message" msgid="6142269839066172984">"Acum telefonul funcționează normal.\nAtinge pentru mai multe informații"</string> + <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonul se încălzise prea mult și s-a oprit pentru a se răci. Acum telefonul funcționează normal.\n\nTelefonul s-ar putea încălzi prea mult dacă:\n • folosești aplicații care consumă multe resurse (de ex., jocuri, aplicații video/de navigare);\n • descarci/încarci fișiere mari;\n • folosești telefonul la temperaturi ridicate."</string> + <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vezi pașii pentru îngrijire"</string> <string name="high_temp_title" msgid="2218333576838496100">"Telefonul se încălzește"</string> - <string name="high_temp_notif_message" msgid="1277346543068257549">"Anumite funcții sunt limitate în timp ce telefonul se răcește.\nAtingeți pentru mai multe informații"</string> - <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonul va încerca automat să se răcească. Puteți folosi telefonul în continuare, dar este posibil să funcționeze mai lent.\n\nDupă ce se răcește, telefonul va funcționa normal."</string> - <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vedeți pașii pentru îngrijire"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> - <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vedeți pașii pentru îngrijire"</string> + <string name="high_temp_notif_message" msgid="1277346543068257549">"Anumite funcții sunt limitate în timp ce telefonul se răcește.\nAtinge pentru mai multe informații"</string> + <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonul va încerca automat să se răcească. Îl poți folosi în continuare, dar e posibil să funcționeze mai lent.\n\nDupă ce se răcește, telefonul va funcționa normal."</string> + <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vezi pașii pentru îngrijire"</string> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deconectează dispozitivul"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Dispozitivul se încălzește lângă portul de încărcare. Dacă este conectat la un încărcător sau accesoriu USB, deconectează-l și ai grijă, deoarece și cablul poate fi cald."</string> + <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vezi pașii pentru îngrijire"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Comanda rapidă din stânga"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Comanda rapidă din dreapta"</string> <string name="lockscreen_unlock_left" msgid="1417801334370269374">"Comanda rapidă din stânga și deblochează"</string> <string name="lockscreen_unlock_right" msgid="4658008735541075346">"Comanda rapidă din dreapta și deblochează"</string> <string name="lockscreen_none" msgid="4710862479308909198">"Niciuna"</string> - <string name="tuner_launch_app" msgid="3906265365971743305">"Lansați <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="tuner_launch_app" msgid="3906265365971743305">"Lansează <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="tuner_other_apps" msgid="7767462881742291204">"Alte aplicații"</string> <string name="tuner_circle" msgid="5270591778160525693">"Cerc"</string> <string name="tuner_plus" msgid="4130366441154416484">"Plus"</string> @@ -701,9 +698,9 @@ <string name="instant_apps" msgid="8337185853050247304">"Aplicații instantanee"</string> <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> rulează"</string> <string name="instant_apps_message" msgid="6112428971833011754">"Aplicația a fost deschisă fără a fi instalată."</string> - <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Aplicația a fost deschisă fără a fi instalată. Atingeți pentru a afla mai multe."</string> + <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Aplicația a fost deschisă fără a fi instalată. Atinge pentru a afla mai multe."</string> <string name="app_info" msgid="5153758994129963243">"Informații aplicație"</string> - <string name="go_to_web" msgid="636673528981366511">"Accesați browserul"</string> + <string name="go_to_web" msgid="636673528981366511">"Accesează browserul"</string> <string name="mobile_data" msgid="4564407557775397216">"Date mobile"</string> <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string> @@ -714,21 +711,21 @@ <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"Funcția Nu deranja a fost activată de o aplicație (<xliff:g id="ID_1">%s</xliff:g>)."</string> <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"Funcția Nu deranja a fost activată de o regulă automată sau de o aplicație."</string> <string name="running_foreground_services_title" msgid="5137313173431186685">"Aplicațiile rulează în fundal"</string> - <string name="running_foreground_services_msg" msgid="3009459259222695385">"Atingeți pentru mai multe detalii privind bateria și utilizarea datelor"</string> - <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivați datele mobile?"</string> - <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu veți avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string> - <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul dvs."</string> - <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu vă pot verifica răspunsul."</string> - <string name="slice_permission_title" msgid="3262615140094151017">"Permiteți <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string> + <string name="running_foreground_services_msg" msgid="3009459259222695385">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string> + <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string> + <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string> + <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string> + <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string> + <string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="slice_permission_text_2" msgid="6758906940360746983">"- Poate efectua acțiuni în <xliff:g id="APP">%1$s</xliff:g>"</string> - <string name="slice_permission_checkbox" msgid="4242888137592298523">"Permiteți <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string> - <string name="slice_permission_allow" msgid="6340449521277951123">"Permiteți"</string> - <string name="slice_permission_deny" msgid="6870256451658176895">"Refuzați"</string> - <string name="auto_saver_title" msgid="6873691178754086596">"Atingeți pentru a programa Economisirea energiei"</string> - <string name="auto_saver_text" msgid="3214960308353838764">"Porniți dacă este probabil ca bateria să se descarce"</string> + <string name="slice_permission_checkbox" msgid="4242888137592298523">"Permite <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string> + <string name="slice_permission_allow" msgid="6340449521277951123">"Permite"</string> + <string name="slice_permission_deny" msgid="6870256451658176895">"Refuz"</string> + <string name="auto_saver_title" msgid="6873691178754086596">"Atinge pentru a programa Economisirea energiei"</string> + <string name="auto_saver_text" msgid="3214960308353838764">"Pornește dacă e probabil ca bateria să se descarce"</string> <string name="no_auto_saver_action" msgid="7467924389609773835">"Nu, mulțumesc"</string> - <string name="heap_dump_tile_name" msgid="2464189856478823046">"Extrageți memoria SysUI"</string> + <string name="heap_dump_tile_name" msgid="2464189856478823046">"Extrage memoria SysUI"</string> <string name="ongoing_privacy_dialog_a11y_title" msgid="2205794093673327974">"În uz"</string> <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplicațiile folosesc <xliff:g id="TYPES_LIST">%s</xliff:g>."</string> <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string> @@ -775,16 +772,16 @@ <string name="accessibility_magnifier_edit" msgid="1522877239671820636">"Editează"</string> <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setările ferestrei de mărire"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atingeți pentru a deschide funcțiile de accesibilitate. Personalizați sau înlocuiți butonul în Setări.\n\n"<annotation id="link">"Afișați setările"</annotation></string> - <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mutați butonul spre margine pentru a-l ascunde temporar"</string> - <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mutați în stânga sus"</string> - <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mutați în dreapta sus"</string> - <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mutați în stânga jos"</string> - <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mutați în dreapta jos"</string> + <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string> + <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string> + <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mută în dreapta sus"</string> + <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mută în stânga jos"</string> + <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mută în dreapta jos"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mutați în afară și ascundeți"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mutați în afară și afișați"</string> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Activați / dezactivați"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string> - <string name="controls_providers_title" msgid="6879775889857085056">"Alegeți aplicația pentru a adăuga comenzi"</string> + <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string> <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S-a adăugat # comandă.}few{S-au adăugat # comenzi.}other{S-au adăugat # de comenzi.}}"</string> <string name="controls_removed" msgid="3731789252222856959">"Eliminată"</string> <string name="accessibility_control_favorite" msgid="8694362691985545985">"Marcată ca preferată"</string> @@ -792,18 +789,18 @@ <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"S-a anulat marcarea ca preferată"</string> <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"marcați ca preferată"</string> <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"anulați marcarea ca preferată"</string> - <string name="accessibility_control_move" msgid="8980344493796647792">"Mutați pe poziția <xliff:g id="NUMBER">%d</xliff:g>"</string> + <string name="accessibility_control_move" msgid="8980344493796647792">"Mută pe poziția <xliff:g id="NUMBER">%d</xliff:g>"</string> <string name="controls_favorite_default_title" msgid="967742178688938137">"Comenzi"</string> - <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Alegeți comenzile de accesat din Setările rapide"</string> + <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Alege comenzile de accesat din Setările rapide"</string> <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Țineți apăsat și trageți pentru a rearanja comenzile"</string> <string name="controls_favorite_removed" msgid="5276978408529217272">"Au fost șterse toate comenzile"</string> <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Modificările nu au fost salvate"</string> - <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Vedeți alte aplicații"</string> + <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Vezi alte aplicații"</string> <string name="controls_favorite_load_error" msgid="5126216176144877419">"Comenzile nu au putut fi încărcate. Accesați aplicația <xliff:g id="APP">%s</xliff:g> pentru a vă asigura că setările aplicației nu s-au schimbat."</string> <string name="controls_favorite_load_none" msgid="7687593026725357775">"Nu sunt disponibile comenzi compatibile"</string> <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Altul"</string> - <string name="controls_dialog_title" msgid="2343565267424406202">"Adăugați la comenzile dispozitivelor"</string> - <string name="controls_dialog_ok" msgid="2770230012857881822">"Adăugați"</string> + <string name="controls_dialog_title" msgid="2343565267424406202">"Adaugă la comenzile dispozitivelor"</string> + <string name="controls_dialog_ok" msgid="2770230012857881822">"Adaugă"</string> <string name="controls_dialog_message" msgid="342066938390663844">"Sugerat de <xliff:g id="APP">%s</xliff:g>"</string> <string name="controls_tile_locked" msgid="731547768182831938">"Dispozitiv blocat"</string> <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Vedeți și controlați dispozitivele de pe ecranul de blocare?"</string> @@ -815,10 +812,10 @@ <string name="controls_pin_use_alphanumeric" msgid="8478371861023048414">"Codul PIN conține litere sau simboluri"</string> <string name="controls_pin_verify" msgid="3452778292918877662">"Verificați <xliff:g id="DEVICE">%s</xliff:g>"</string> <string name="controls_pin_wrong" msgid="6162694056042164211">"Cod PIN greșit"</string> - <string name="controls_pin_instructions" msgid="6363309783822475238">"Introduceți codul PIN"</string> - <string name="controls_pin_instructions_retry" msgid="1566667581012131046">"Încercați alt cod PIN"</string> - <string name="controls_confirmation_message" msgid="7744104992609594859">"Confirmați schimbarea pentru <xliff:g id="DEVICE">%s</xliff:g>"</string> - <string name="controls_structure_tooltip" msgid="4355922222944447867">"Glisați pentru a vedea mai multe"</string> + <string name="controls_pin_instructions" msgid="6363309783822475238">"Introdu codul PIN"</string> + <string name="controls_pin_instructions_retry" msgid="1566667581012131046">"Încearcă alt cod PIN"</string> + <string name="controls_confirmation_message" msgid="7744104992609594859">"Confirmă schimbarea pentru <xliff:g id="DEVICE">%s</xliff:g>"</string> + <string name="controls_structure_tooltip" msgid="4355922222944447867">"Glisează pentru a vedea mai multe"</string> <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Se încarcă recomandările"</string> <string name="controls_media_title" msgid="1746947284862928133">"Media"</string> <string name="controls_media_close_session" msgid="4780485355795635052">"Ascundeți comanda media pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> @@ -828,36 +825,36 @@ <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> - <string name="controls_media_button_play" msgid="2705068099607410633">"Redați"</string> - <string name="controls_media_button_pause" msgid="8614887780950376258">"Întrerupeți"</string> + <string name="controls_media_button_play" msgid="2705068099607410633">"Redă"</string> + <string name="controls_media_button_pause" msgid="8614887780950376258">"Întrerupe"</string> <string name="controls_media_button_prev" msgid="8126822360056482970">"Melodia anterioară"</string> <string name="controls_media_button_next" msgid="6662636627525947610">"Melodia următoare"</string> <string name="controls_media_button_connecting" msgid="3138354625847598095">"Se conectează"</string> - <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string> - <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> - <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> - <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> - <string name="media_transfer_undo" msgid="1895606387620728736">"Anulați"</string> + <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redă"</string> + <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschide <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> + <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> + <string name="media_transfer_undo" msgid="1895606387620728736">"Anulează"</string> <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropiați-vă pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Mergeți mai aproape de <xliff:g id="DEVICENAME">%1$s</xliff:g> ca să redați acolo"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> - <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încercați din nou."</string> + <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string> <string name="controls_error_removed_message" msgid="2885911717034750542">"Nu s-a putut accesa <xliff:g id="DEVICE">%1$s</xliff:g>. Accesați aplicația <xliff:g id="APPLICATION">%2$s</xliff:g> pentru a vă asigura de disponibilitatea comenzii și că setările aplicației nu s-au schimbat."</string> - <string name="controls_open_app" msgid="483650971094300141">"Deschideți aplicația"</string> + <string name="controls_open_app" msgid="483650971094300141">"Deschide aplicația"</string> <string name="controls_error_generic" msgid="352500456918362905">"Starea nu se poate încărca"</string> <string name="controls_error_failed" msgid="960228639198558525">"Eroare, încercați din nou"</string> - <string name="controls_menu_add" msgid="4447246119229920050">"Adăugați comenzi"</string> - <string name="controls_menu_edit" msgid="890623986951347062">"Editați comenzile"</string> - <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adăugați ieșiri"</string> + <string name="controls_menu_add" msgid="4447246119229920050">"Adaugă comenzi"</string> + <string name="controls_menu_edit" msgid="890623986951347062">"Editează comenzile"</string> + <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adaugă ieșiri"</string> <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string> <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string> <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S-au selectat <xliff:g id="COUNT">%1$d</xliff:g> dispozitive"</string> <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deconectat)"</string> - <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Nu se poate comuta. Atingeți pentru a încerca din nou."</string> - <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Conectați un dispozitiv"</string> + <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Nu se poate comuta. Atinge pentru a încerca din nou."</string> + <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Conectează un dispozitiv"</string> <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Pentru a proiecta această sesiune, deschideți aplicația."</string> <string name="media_output_dialog_unknown_launch_app_name" msgid="1084899329829371336">"Aplicație necunoscută"</string> <string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Nu mai proiectați"</string> @@ -869,14 +866,14 @@ <string name="media_output_broadcasting_message" msgid="4150299923404886073">"Ca să asculte transmisia dvs., persoanele din apropiere cu dispozitive Bluetooth compatibile vă pot scana codul QR sau pot folosi numele și parola transmisiei."</string> <string name="media_output_broadcast_name" msgid="8786127091542624618">"Numele transmisiei"</string> <string name="media_output_broadcast_code" msgid="870795639644728542">"Parolă"</string> - <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Salvați"</string> + <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Salvează"</string> <string name="media_output_broadcast_starting" msgid="8130153654166235557">"Începe…"</string> <string name="media_output_broadcast_start_failed" msgid="3670835946856129775">"Nu se poate transmite"</string> - <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"Nu se poate salva. Încercați din nou."</string> + <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"Nu se poate salva. Încearcă din nou."</string> <string name="media_output_broadcast_last_update_error" msgid="5484328807296895491">"Nu se poate salva."</string> <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string> <string name="build_number_copy_toast" msgid="877720921605503046">"Numărul versiunii s-a copiat în clipboard."</string> - <string name="basic_status" msgid="2315371112182658176">"Deschideți conversația"</string> + <string name="basic_status" msgid="2315371112182658176">"Deschide conversația"</string> <string name="select_conversation_title" msgid="6716364118095089519">"Widgeturi pentru conversație"</string> <string name="select_conversation_text" msgid="3376048251434956013">"Atingeți o conversație ca să o adăugați pe ecranul de pornire"</string> <string name="no_conversations_text" msgid="5354115541282395015">"Conversațiile dvs. recente se vor afișa aici"</string> @@ -905,7 +902,7 @@ <string name="status_before_loading" msgid="1500477307859631381">"Conținutul va apărea în curând"</string> <string name="missed_call" msgid="4228016077700161689">"Apel nepreluat"</string> <string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string> - <string name="people_tile_description" msgid="8154966188085545556">"Vedeți mesaje recente, apeluri pierdute și actualizări de stare"</string> + <string name="people_tile_description" msgid="8154966188085545556">"Vezi mesaje recente, apeluri pierdute și actualizări de stare"</string> <string name="people_tile_title" msgid="6589377493334871272">"Conversație"</string> <string name="paused_by_dnd" msgid="7856941866433556428">"Întrerupt de Nu deranja"</string> <string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> a trimis un mesaj: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string> @@ -913,11 +910,11 @@ <string name="new_status_content_description" msgid="6046637888641308327">"<xliff:g id="NAME">%1$s</xliff:g> are o nouă stare: <xliff:g id="STATUS">%2$s</xliff:g>"</string> <string name="person_available" msgid="2318599327472755472">"Disponibil"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problemă la citirea măsurării bateriei"</string> - <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Atingeți pentru mai multe informații"</string> + <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Atinge pentru mai multe informații"</string> <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nicio alarmă setată"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor de amprentă"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Autentificați-vă"</string> - <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesați dispozitivul"</string> + <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesează dispozitivul"</string> <string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Folosiți amprenta ca să deschideți"</string> <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Autentificare obligatorie. Atingeți senzorul de amprentă pentru a vă autentifica."</string> <string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Apel telefonic în desfășurare"</string> @@ -930,32 +927,32 @@ <string name="all_network_unavailable" msgid="4112774339909373349">"Nicio rețea disponibilă"</string> <string name="turn_on_wifi" msgid="1308379840799281023">"Wi-Fi"</string> <string name="tap_a_network_to_connect" msgid="1565073330852369558">"Atingeți o rețea pentru a vă conecta"</string> - <string name="unlock_to_view_networks" msgid="5072880496312015676">"Deblocați pentru a vedea rețelele"</string> + <string name="unlock_to_view_networks" msgid="5072880496312015676">"Deblochează pentru a vedea rețelele"</string> <string name="wifi_empty_list_wifi_on" msgid="3864376632067585377">"Se caută rețele…"</string> <string name="wifi_failed_connect_message" msgid="4161863112079000071">"Nu s-a realizat conexiunea la rețea"</string> <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Deocamdată, Wi-Fi nu se poate conecta automat"</string> <string name="see_all_networks" msgid="3773666844913168122">"Afișează-le pe toate"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pentru a schimba rețeaua, deconectați ethernet"</string> <string name="wifi_scan_notify_message" msgid="3753839537448621794">"Pentru a îmbunătăți experiența cu dispozitivul, aplicațiile și serviciile pot să caute în continuare rețele Wi‑Fi chiar și atunci când conexiunea Wi-Fi este dezactivată. Puteți să schimbați acest aspect din setările pentru căutarea de rețele Wi-Fi. "<annotation id="link">"Schimbați"</annotation></string> - <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Dezactivați modul Avion"</string> + <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Dezactivează modul Avion"</string> <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> vrea să adauge următorul card la Setări rapide"</string> - <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Adăugați un card"</string> + <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Adaugă un card"</string> <string name="qs_tile_request_dialog_not_add" msgid="4168716573114067296">"Nu adăugați un card"</string> - <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alege utilizatorul"</string> <string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# aplicație este activă}few{# aplicații sunt active}other{# de aplicații sunt active}}"</string> <string name="fgs_dot_content_description" msgid="2865071539464777240">"Informații noi"</string> <string name="fgs_manager_dialog_title" msgid="5879184257257718677">"Aplicații active"</string> <string name="fgs_manager_dialog_message" msgid="2670045017200730076">"Aceste aplicații sunt active și rulează, chiar dacă nu le folosiți. Astfel, funcțiile lor sunt îmbunătățite, dar autonomia bateriei poate fi afectată."</string> - <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Opriți"</string> + <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Oprește"</string> <string name="fgs_manager_app_item_stop_button_stopped_label" msgid="6950382004441263922">"Oprită"</string> <string name="clipboard_edit_text_done" msgid="4551887727694022409">"Gata"</string> <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"S-a copiat"</string> <string name="clipboard_edit_source" msgid="9156488177277788029">"Din <xliff:g id="APPNAME">%1$s</xliff:g>"</string> - <string name="clipboard_dismiss_description" msgid="3335990369850165486">"Închideți textul copiat"</string> - <string name="clipboard_edit_text_description" msgid="805254383912962103">"Editați textul copiat"</string> - <string name="clipboard_edit_image_description" msgid="8904857948976041306">"Editați imaginea copiată"</string> - <string name="clipboard_send_nearby_description" msgid="4629769637846717650">"Trimiteți către un dispozitiv din apropiere"</string> - <string name="clipboard_text_hidden" msgid="7926899867471812305">"Atingeți pentru a afișa"</string> + <string name="clipboard_dismiss_description" msgid="3335990369850165486">"Închide textul copiat"</string> + <string name="clipboard_edit_text_description" msgid="805254383912962103">"Editează textul copiat"</string> + <string name="clipboard_edit_image_description" msgid="8904857948976041306">"Editează imaginea copiată"</string> + <string name="clipboard_send_nearby_description" msgid="4629769637846717650">"Trimite către un dispozitiv din apropiere"</string> + <string name="clipboard_text_hidden" msgid="7926899867471812305">"Atinge pentru a afișa"</string> <string name="clipboard_text_copied" msgid="5100836834278976679">"Textul a fost copiat"</string> <string name="clipboard_image_copied" msgid="3793365360174328722">"Imaginea a fost copiată"</string> <string name="clipboard_content_copied" msgid="144452398567828145">"Conținutul a fost copiat"</string> @@ -963,8 +960,8 @@ <string name="clipboard_overlay_window_name" msgid="6450043652167357664">"Clipboard"</string> <string name="clipboard_image_preview" msgid="2156475174343538128">"Previzualizarea imaginii"</string> <string name="clipboard_edit" msgid="4500155216174011640">"editați"</string> - <string name="add" msgid="81036585205287996">"Adăugați"</string> - <string name="manage_users" msgid="1823875311934643849">"Gestionați utilizatorii"</string> + <string name="add" msgid="81036585205287996">"Adaugă"</string> + <string name="manage_users" msgid="1823875311934643849">"Gestionează utilizatorii"</string> <string name="drag_split_not_supported" msgid="4326847447699729722">"Notificarea nu acceptă tragerea pe ecranul împărțit."</string> <string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"Wi‑Fi indisponibil"</string> <string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Modul Prioritate"</string> @@ -978,7 +975,7 @@ <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Opriți difuzarea <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Dacă difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbați rezultatul, difuzarea actuală se va opri"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string> - <string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Schimbați rezultatul"</string> + <string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Schimbă rezultatul"</string> <string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Necunoscută"</string> <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE, z LLL"</string> <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index e848857f1ecc..08eee34c0c80 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Вкл."</string> <string name="switch_bar_off" msgid="5669805115416379556">"Откл."</string> <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"узнать больше"</string> <string name="nav_bar" msgid="4642708685386136807">"Панель навигации"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Расположение кнопок"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Дополнительный тип кнопки \"Влево\""</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Пока телефон не остынет, некоторые функции могут быть недоступны.\nНажмите, чтобы получить дополнительную информацию"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш телефон остынет автоматически.\n\nОбратите внимание, что до тех пор он может работать медленнее, чем обычно."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Подробнее о действиях при перегреве…"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Отключите устройство"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Устройство нагревается в районе зарядного порта. Если оно подключено к зарядному или USB-устройству, отключите его. Будьте осторожны: кабель тоже мог нагреться."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Подробнее о действиях при перегреве…"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ярлык слева"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Ярлык справа"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index fcff90dac09f..c329c22071e1 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ක්රියාත්මකයි"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ක්රියාවිරහිතයි"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ලබා ගත නොහැකිය"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"තව දැන ගන්න"</string> <string name="nav_bar" msgid="4642708685386136807">"සංචලන තීරුව"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"පිරිසැලසුම"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"අමතර වම් බොත්තම් වර්ගය"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"දුරකථනය සිසිල් වන අතරතුර සමහර විශේෂාංග සීමිත විය හැකිය.\nතව තතු සඳහා තට්ටු කරන්න"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"ඔබගේ දුරකථනය ස්වයංක්රියව සිසිල් වීමට උත්සාහ කරනු ඇත. ඔබට තවම ඔබේ දුරකථනය භාවිත කළ හැකිය, නමුත් එය සෙමින් ධාවනය විය හැකිය.\n\nඔබේ දුරකථනය සිසිල් වූ පසු, එය සාමාන්ය ලෙස ධාවනය වනු ඇත."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"රැකවරණ පියවර බලන්න"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ඔබේ උපාංගය ගලවන්න"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ඔබේ උපාංගය ආරෝපණ කවුළුව අවට උණුසුම් වෙමින් පවතී. එය චාජරයකට හෝ USB උපාංගයකට සම්බන්ධ කර ඇත්නම්, එය ගලවා, කේබලය උණුසුම් විය හැකි බැවින් ප්රවේශම් වන්න."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"රැකවරණ පියවර බලන්න"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"වම් කෙටි මග"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"දකුණු කෙටි මග"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index eefb1de38701..9f9efc409c07 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Niektoré funkcie budú obmedzené, dokým neklesne teplota telefónu.\nViac sa dozviete po klepnutí."</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Váš telefón sa automaticky pokúsi schladiť. Môžete ho naďalej používať, ale môže fungovať pomalšie.\n\nPo poklese teploty bude telefón fungovať ako normálne."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobraziť opatrenia"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zariadenie"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Zariadenie sa zahrieva pri nabíjacom porte. Ak je pripojené k nabíjačke alebo príslušenstvu USB, odpojte ho a dajte pozor, lebo môže byť horúci aj kábel."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobraziť opatrenia"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ľavá skratka"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pravá skratka"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 914036166684..397659d7a90b 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Vklopljeno"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Izklopljeno"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Ni na voljo"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"za več informacij"</string> <string name="nav_bar" msgid="4642708685386136807">"Vrstica za krmarjenje"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Postavitev"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnega levega gumba"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Nekatere funkcije bodo med ohlajanjem telefona omejene.\nDotaknite se za več informacij"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon se bo samodejno poskusil ohladiti. Še naprej ga lahko uporabljate, vendar bo morda deloval počasneje.\n\nKo se telefon ohladi, bo zopet deloval kot običajno."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Oglejte si navodila za ukrepanje"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odklopite napravo"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Naprava se segreva pri vratih za polnjenje. Če je priključena na polnilnik ali dodatek USB, ga odklopite in bodite tem previdni, saj je tudi kabel lahko topel."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Oglejte si navodila za ukrepanje"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Leva bližnjica"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desna bližnjica"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 99536cb2e2bc..f7144115e6d2 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Aktiv"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Joaktiv"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Nuk ofrohet"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"mëso më shumë"</string> <string name="nav_bar" msgid="4642708685386136807">"Shiriti i navigimit"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Struktura"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Lloji i butonit shtesë majtas"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Disa veçori janë të kufizuara kur telefoni është duke u ftohur.\nTrokit për më shumë informacione"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefoni yt do të përpiqet automatikisht që të ftohet. Mund ta përdorësh përsëri telefonin, por ai mund të punojë më ngadalë.\n\nPasi telefoni të jetë ftohur, ai do të punojë si normalisht."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Shiko hapat për kujdesin"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Shkëpute pajisjen"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Pajisja jote po nxehet pranë portës së karikimit. Nëse është lidhur me një karikues ose një aksesor USB, shkëpute dhe trego kujdes pasi kablloja mund të jetë e nxehtë po ashtu."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Shiko hapat për kujdesin"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Shkurtorja majtas"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Shkurtorja djathtas"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 8b9822d7b7c6..9eabe28b26b1 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Укључено"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Искључено"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"сазнајте више"</string> <string name="nav_bar" msgid="4642708685386136807">"Трака за навигацију"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Распоред"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Додатни тип левог дугмета"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Неке функције су ограничене док се телефон не охлади.\nДодирните за више информација"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефон ће аутоматски покушати да се охлади. И даље ћете моћи да користите телефон, али ће спорије реаговати.\n\nКада се телефон охлади, нормално ће радити."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Погледајте упозорења"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Искључите уређај"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Уређај се загрева у близини порта за пуњење. Ако је повезан са пуњачем или USB опремом, искључите је и будите пажљиви јер и кабл може да буде врућ."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Погледајте упозорења"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Лева пречица"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десна пречица"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 374d5521fa83..c7446fa46aab 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"På"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Av"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Inte tillgängligt"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"läs mer"</string> <string name="nav_bar" msgid="4642708685386136807">"Navigeringsfält"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Knapptyp för extra vänster"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Vissa funktioner är begränsade medan telefonen svalnar.\nTryck för mer information"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Mobilen försöker svalna automatiskt. Du kan fortfarande använda mobilen, men den kan vara långsammare än vanligt.\n\nMobilen fungerar som vanligt när den har svalnat."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Visa alla skötselråd"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koppla ur enheten"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Enheten börjar bli varm vid laddningsporten. Om den är ansluten till en laddare eller ett USB-tillbehör kopplar du ur den. Var försiktigt eftersom kabeln också kan vara varm."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Visa alla skötselråd"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vänster genväg"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Höger genväg"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 79e542ff626f..c02ea6cf1859 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Imewashwa"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Imezimwa"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Hakipatikani"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"pata maelezo zaidi"</string> <string name="nav_bar" msgid="4642708685386136807">"Sehemu ya viungo muhimu"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Mpangilio"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Aina ya kitufe cha kushoto cha ziada"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Baadhi ya vipengele havitatumika kwenye simu wakati inapoa.\nGusa ili upate maelezo zaidi"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Simu yako itajaribu kupoa kiotomatiki. Bado unaweza kutumia simu yako, lakini huenda ikafanya kazi polepole. \n\nPindi simu yako itakapopoa, itaendelea kufanya kazi kama kawaida."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Angalia hatua za utunzaji"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Chomoa kifaa chako"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Kifaa chako kinapata joto karibu na mlango wa kuchaji. Ikiwa kimeunganishwa kwenye chaja au kifuasi cha USB, kichomoe na uwe makini kwani kebo inaweza kuwa imepata joto."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Angalia hatua za ulinzi"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Njia ya mkato ya kushoto"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Njia ya mkato ya kulia"</string> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index f4d482406fa0..b24ce122208f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -66,8 +66,6 @@ <dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_notifications_scrim_transition_distance</dimen> <dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_notifications_scrim_transition_delay</dimen> <dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen> - <!-- On split-shade, the QS squish transition should start from half height. --> - <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item> <!-- On split-shade, there should be no depth effect, so setting the value to 0. --> <dimen name="lockscreen_shade_depth_controller_transition_distance">0dp</dimen> <dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index a587e5a806ca..5dcbeb5c85cf 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -86,8 +86,6 @@ <dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_scrim_transition_distance</dimen> <dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen> - <!-- On large screen portrait, the QS squish transition should start from half height. --> - <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item> <dimen name="lockscreen_shade_depth_controller_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 1c795bf6b6d8..303e43588536 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"ஆன்"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ஆஃப்"</string> <string name="tile_unavailable" msgid="3095879009136616920">"இல்லை"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"மேலும் அறிக"</string> <string name="nav_bar" msgid="4642708685386136807">"வழிசெலுத்தல் பட்டி"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"தளவமைப்பு"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"கூடுதல் இடப்புற பட்டன் வகை"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"மொபைலின் வெப்ப அளவு குறையும் வரை சில அம்சங்களைப் பயன்படுத்த முடியாது.\nமேலும் தகவலுக்கு தட்டவும்"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"உங்கள் மொபைலின் வெப்ப அளவு தானாகவே குறையும். தொடர்ந்து நீங்கள் மொபைலைப் பயன்படுத்தலாம், ஆனால் அதன் வேகம் குறைவாக இருக்கக்கூடும்.\n\nமொபைலின் வெப்ப அளவு குறைந்தவுடன், அது இயல்பு நிலையில் இயங்கும்."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"சாதன இணைப்பைத் துண்டித்தல்"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"சார்ஜிங் போர்ட்டிற்கு அருகே உங்கள் சாதனம் சூடாகிறது. சார்ஜருடனோ USB உபகரணத்துடனோ சாதனம் இணைக்கப்பட்டிருந்தால் அதன் இணைப்பைத் துண்டிக்கவும். கேபிளும் சூடாக இருக்கக்கூடும் என்பதால் கவனத்துடன் கையாளவும்."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"இடப்புற ஷார்ட்கட்"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"வலப்புற ஷார்ட்கட்"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index b278f22cefcf..643fe1ec262f 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ఫోన్ను చల్లబరిచే క్రమంలో కొన్ని ఫీచర్లు పరిమితం చేయబడ్డాయి.\nమరింత సమాచారం కోసం ట్యాప్ చేయండి"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"మీ ఫోన్ ఆటోమేటిక్గా చల్లబడటానికి ప్రయత్నిస్తుంది. మీరు ఇప్పటికీ మీ ఫోన్ను ఉపయోగించవచ్చు, కానీ దాని పనితీరు నెమ్మదిగా ఉండవచ్చు.\n\nమీ ఫోన్ చల్లబడిన తర్వాత, అది సాధారణ రీతిలో పని చేస్తుంది."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"మీ పరికరాన్ని అన్ప్లగ్ చేయండి"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ఛార్జింగ్ పోర్ట్ దగ్గర ఉంచినప్పుడు మీ పరికరం వేడెక్కుతోంది. ఇది ఛార్జర్ లేదా USB యాక్సెసరీకి కనెక్ట్ చేసి ఉంటే, దాన్ని అన్ప్లగ్ చేసి, కేబుల్ వేడెక్కే అవకాశం కూడా ఉన్నందున జాగ్రత్త వహించండి."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ఎడమవైపు షార్ట్కట్"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"కుడివైపు షార్ట్కట్"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index ec0e36595137..0c20911c4d42 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"เปิด"</string> <string name="switch_bar_off" msgid="5669805115416379556">"ปิด"</string> <string name="tile_unavailable" msgid="3095879009136616920">"ไม่พร้อมใช้งาน"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ดูข้อมูลเพิ่มเติม"</string> <string name="nav_bar" msgid="4642708685386136807">"แถบนำทาง"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"การจัดวาง"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ประเภทปุ่มทางซ้ายเพิ่มเติม"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"ฟีเจอร์บางอย่างจะใช้งานได้จำกัดขณะโทรศัพท์เย็นลง\nแตะเพื่อดูข้อมูลเพิ่มเติม"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"โทรศัพท์จะพยายามลดอุณหภูมิลงโดยอัตโนมัติ คุณยังสามารถใช้โทรศัพท์ได้ แต่โทรศัพท์อาจทำงานช้าลง\n\nโทรศัพท์จะทำงานตามปกติเมื่อเย็นลงแล้ว"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ดูขั้นตอนในการดูแลรักษา"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"ถอดปลั๊กอุปกรณ์"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"บริเวณพอร์ตชาร์จของอุปกรณ์เริ่มจะร้อนแล้ว หากมีที่ชาร์จหรืออุปกรณ์เสริม USB เสียบอยู่ ให้ถอดออกอย่างระมัดระวังเพราะสายเส้นนั้นก็อาจจะร้อนด้วยเช่นกัน"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ดูขั้นตอนในการดูแลรักษา"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ทางลัดทางซ้าย"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ทางลัดทางขวา"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index c0b358860022..4653b79336c3 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Limitado ang ilang feature habang nagku-cool down ang telepono.\nMag-tap para sa higit pang impormasyon"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Awtomatikong susubukan ng iyong telepono na mag-cool down. Magagamit mo pa rin ang iyong telepono, ngunit maaaring mas mabagal ang paggana nito.\n\nKapag nakapag-cool down na ang iyong telepono, gagana na ito nang normal."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Tingnan ang mga hakbang sa pangangalaga"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Bunutin sa saksakan ang device"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Umiinit ang iyong device malapit sa charging port. Kung nakakonekta ito sa charger o USB accessory, bunutin ito sa saksakan, at mag-ingat dahil posibleng mainit din ang cable."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Tingnan ang mga hakbang sa pangangalaga"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Kaliwang shortcut"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Kanang shortcut"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 389178a15322..2629f10a4504 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Açık"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Kapalı"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Kullanılamıyor"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"daha fazla bilgi"</string> <string name="nav_bar" msgid="4642708685386136807">"Gezinme çubuğu"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Düzen"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra sol düğme türü"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Telefon soğurken bazı özellikler sınırlı olarak kullanılabilir.\nDaha fazla bilgi için dokunun"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonunuz otomatik olarak soğumaya çalışacak. Bu sırada telefonunuzu kullanmaya devam edebilirsiniz ancak uygulamalar daha yavaş çalışabilir.\n\nTelefonunuz soğuduktan sonra normal şekilde çalışacaktır."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bakımla ilgili adımlara bakın"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızın fişini çekin"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Cihazınız, şarj yuvasının yakınındayken ısınıyor. Şarj cihazına veya USB aksesuarına bağlıysa cihazı çıkarın. Ayrıca, kablo sıcak olabileceği için dikkatli olun."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Bakımla ilgili adımlara bakın"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Sol kısayol"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Sağ kısayol"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 77966e8a7bb1..aea9d4ee0a8e 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Увімкнено"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Вимкнено"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"дізнатися більше"</string> <string name="nav_bar" msgid="4642708685386136807">"Панель навігації"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Макет"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Додатковий тип кнопки ліворуч"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Під час охолодження деякі функції обмежуються.\nНатисніть, щоб дізнатися більше"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш телефон охолоджуватиметься автоматично. Ви можете далі користуватися телефоном, але він може працювати повільніше.\n\nКоли телефон охолоне, він працюватиме належним чином."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Переглянути запобіжні заходи"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Від’єднайте пристрій"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Пристрій нагрівається біля зарядного порту. Якщо він під’єднаний до зарядного пристрою або USB-аксесуара, від’єднайте його, однак будьте обережні, оскільки кабель також може бути гарячий."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Переглянути застереження"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Комбінація клавіш ліворуч"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Комбінація клавіш праворуч"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 4ebbc705d607..e3d2d531a6be 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"آن"</string> <string name="switch_bar_off" msgid="5669805115416379556">"آف"</string> <string name="tile_unavailable" msgid="3095879009136616920">"غیر دستیاب ہے"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"مزید جانیں"</string> <string name="nav_bar" msgid="4642708685386136807">"نیویگیشن بار"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"لے آؤٹ"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"بائيں جانب کی اضافی بٹن کی قسم"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"فون کے ٹھنڈے ہو جانے تک کچھ خصوصیات محدود ہیں۔\nمزید معلومات کیلئے تھپتھپائیں"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"آپ کا فون خودکار طور پر ٹھنڈا ہونے کی کوشش کرے گا۔ آپ ابھی بھی اپنا فون استعمال کر سکتے ہیں، مگر ہو سکتا ہے یہ سست چلے۔\n\nایک بار آپ کا فون ٹھنڈا ہوجائے تو یہ معمول کے مطابق چلے گا۔"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"نگہداشت کے اقدامات ملاحظہ کریں"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"اپنے آلہ کو ان پلگ کریں"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"آپ کا آلہ چارجنگ پورٹ کے قریب گرم ہو رہا ہے۔ اگر یہ چارجر یا USB لوازمات سے منسلک ہے تو اسے ان پلگ کریں اور خیال رکھیں کہ کیبل بھی گرم ہو سکتی ہے۔"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"نگہداشت کے اقدامات ملاحظہ کریں"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"بائيں جانب کا شارٹ کٹ"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"دائیں جانب کا شارٹ کٹ"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index c0e7b2f82ea3..68be635ace1d 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"Đang bật"</string> <string name="switch_bar_off" msgid="5669805115416379556">"Đang tắt"</string> <string name="tile_unavailable" msgid="3095879009136616920">"Không có sẵn"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"tìm hiểu thêm"</string> <string name="nav_bar" msgid="4642708685386136807">"Thanh điều hướng"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"Bố cục"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Loại nút bổ sung bên trái"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Một số tính năng bị hạn chế trong khi điện thoại nguội dần.\nHãy nhấn để biết thêm thông tin"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Điện thoại của bạn sẽ tự động nguội dần. Bạn vẫn có thể sử dụng điện thoại, nhưng điện thoại có thể chạy chậm hơn. \n\nSau khi đã nguội, điện thoại sẽ chạy bình thường."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Xem các bước chăm sóc"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Rút thiết bị ra"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Phần gần cổng sạc của thiết bị đang nóng lên. Nếu thiết bị kết nối với bộ sạc hoặc phụ kiện USB, hãy rút ra một cách thận trọng vì cáp có thể cũng đang nóng."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Xem các bước chăm sóc"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lối tắt bên trái"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Lối tắt bên phải"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index df0b0e056cf0..04df91b94aa8 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"开启"</string> <string name="switch_bar_off" msgid="5669805115416379556">"关闭"</string> <string name="tile_unavailable" msgid="3095879009136616920">"不可用"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"了解详情"</string> <string name="nav_bar" msgid="4642708685386136807">"导航栏"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"布局"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按钮类型"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"手机降温时,部分功能的使用会受限制。\n点按即可了解详情"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"您的手机将自动尝试降温。您依然可以使用您的手机,但是手机运行速度可能会更慢。\n\n手机降温后,就会恢复正常的运行速度。"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看处理步骤"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔出设备"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"设备的充电接口附近在发热。如果该设备已连接到充电器或 USB 配件,请立即拔掉,并注意充电线也可能会发热。"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看处理步骤"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左快捷方式"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右快捷方式"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml index b476255bc669..6ce948def69e 100644 --- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml @@ -89,7 +89,7 @@ <string-array name="tile_states_color_correction"> <item msgid="2840507878437297682">"不可用"</item> <item msgid="1909756493418256167">"关闭"</item> - <item msgid="4531508423703413340">"开启"</item> + <item msgid="4531508423703413340">"已开启"</item> </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"不可用"</item> @@ -174,6 +174,6 @@ <string-array name="tile_states_dream"> <item msgid="6184819793571079513">"不可用"</item> <item msgid="8014986104355098744">"关闭"</item> - <item msgid="5966994759929723339">"开启"</item> + <item msgid="5966994759929723339">"已开启"</item> </string-array> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 67f94b766153..aedaec6ed9d8 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -161,7 +161,7 @@ <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識面孔,請改用指紋完成驗證。"</string> <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) --> <skip /> - <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string> + <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string> <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string> <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string> @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"開啟"</string> <string name="switch_bar_off" msgid="5669805115416379556">"關閉"</string> <string name="tile_unavailable" msgid="3095879009136616920">"無法使用"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"瞭解詳情"</string> <string name="nav_bar" msgid="4642708685386136807">"導覽列"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"配置"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按鈕類型"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"手機降溫時,部分功能會受限制。\n輕按即可瞭解詳情"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"手機會自動嘗試降溫。您仍可以使用手機,但手機的運作速度可能較慢。\n\n手機降溫後便會恢復正常。"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看保養步驟"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"充電埠附近的裝置溫度正在上升。如裝置正連接充電器或 USB 配件,請拔除裝置並小心安全,因為電線的溫度可能也偏高。"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看保養步驟"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左捷徑"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右捷徑"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 23189d09cdfb..8151cc4a6c0b 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -598,8 +598,7 @@ <string name="switch_bar_on" msgid="1770868129120096114">"開啟"</string> <string name="switch_bar_off" msgid="5669805115416379556">"關閉"</string> <string name="tile_unavailable" msgid="3095879009136616920">"無法使用"</string> - <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) --> - <skip /> + <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"瞭解詳情"</string> <string name="nav_bar" msgid="4642708685386136807">"導覽列"</string> <string name="nav_bar_layout" msgid="4716392484772899544">"配置"</string> <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按鈕類型"</string> @@ -672,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"手機降溫時,某些功能會受限。\n輕觸即可瞭解詳情"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"手機會自動嘗試降溫。你仍可繼續使用手機,但是手機的運作速度可能會較慢。\n\n手機降溫完畢後,就會恢復正常的運作速度。"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看處理步驟"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"裝置的充電埠附近越來越熱。如果裝置已連接充電器或 USB 配件,請立即拔除。此外,電線也可能會變熱,請特別留意。"</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看處理步驟"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左快速鍵"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右快速鍵"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 08cd7fcde557..b2937f8a7cd2 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -671,10 +671,8 @@ <string name="high_temp_notif_message" msgid="1277346543068257549">"Ezinye izici zikhawulelwe ngenkathi ifoni iphola.\nThepha mayelana nolwazi olwengeziwe"</string> <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ifoni yakho izozama ngokuzenzakalela ukuphola. Ungasasebenzisa ifoni yakho, kodwa ingasebenza ngokungasheshi.\n\nUma ifoni yakho isipholile, izosebenza ngokuvamile."</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bona izinyathelo zokunakekelwa"</string> - <!-- no translation found for high_temp_alarm_title (8654754369605452169) --> - <skip /> - <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) --> - <skip /> + <string name="high_temp_alarm_title" msgid="8654754369605452169">"Khipha idivayisi yakho"</string> + <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Idivayisi yakho iqala ukufudumala eduze kwembobo yokushaja. Uma ixhunywe kushaja noma insiza ye-USB, yikhiphe, futhi uqaphele njengoba ikhebuli ingase ifudumale."</string> <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Bona izinyathelo zokunakekelwa"</string> <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Isinqamuleli sangakwesokunxele"</string> <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Isinqamuleli sangakwesokudla"</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6592e143dc1b..ede6260cfdea 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1187,6 +1187,7 @@ <!-- Output switcher panel related dimensions --> <dimen name="media_output_dialog_list_max_height">355dp</dimen> + <dimen name="media_output_dialog_list_item_height">76dp</dimen> <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen> <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen> <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> @@ -1234,7 +1235,7 @@ <!-- The fraction at which the QS "squish" transition should start during the lockscreen shade expansion. 0 is fully collapsed, 1 is fully expanded. --> - <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0</item> + <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0.5</item> <!-- Distance that the full shade transition takes in order for depth of the wallpaper to fully change. --> @@ -1467,6 +1468,8 @@ <dimen name="fgs_manager_list_top_spacing">12dp</dimen> <dimen name="media_projection_app_selector_icon_size">32dp</dimen> + <dimen name="media_projection_app_selector_recents_padding">16dp</dimen> + <dimen name="media_projection_app_selector_loader_size">32dp</dimen> <!-- Dream overlay related dimensions --> <dimen name="dream_overlay_status_bar_height">60dp</dimen> diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml index f9d66ee583da..96a58405a764 100644 --- a/packages/SystemUI/shared/res/values/attrs.xml +++ b/packages/SystemUI/shared/res/values/attrs.xml @@ -25,4 +25,39 @@ <attr name="lockScreenWeight" format="integer" /> <attr name="chargeAnimationDelay" format="integer" /> </declare-styleable> + + <declare-styleable name="DoubleShadowAttrDeclare"> + <attr name="keyShadowBlur" format="dimension" /> + <attr name="keyShadowOffsetX" format="dimension" /> + <attr name="keyShadowOffsetY" format="dimension" /> + <attr name="keyShadowAlpha" format="float" /> + <attr name="ambientShadowBlur" format="dimension" /> + <attr name="ambientShadowOffsetX" format="dimension" /> + <attr name="ambientShadowOffsetY" format="dimension" /> + <attr name="ambientShadowAlpha" format="float" /> + </declare-styleable> + + <declare-styleable name="DoubleShadowTextClock"> + <attr name="keyShadowBlur" /> + <attr name="keyShadowOffsetX" /> + <attr name="keyShadowOffsetY" /> + <attr name="keyShadowAlpha" /> + <attr name="ambientShadowBlur" /> + <attr name="ambientShadowOffsetX" /> + <attr name="ambientShadowOffsetY" /> + <attr name="ambientShadowAlpha" /> + </declare-styleable> + + <declare-styleable name="DoubleShadowTextView"> + <attr name="keyShadowBlur" /> + <attr name="keyShadowOffsetX" /> + <attr name="keyShadowOffsetY" /> + <attr name="keyShadowAlpha" /> + <attr name="ambientShadowBlur" /> + <attr name="ambientShadowOffsetX" /> + <attr name="ambientShadowOffsetY" /> + <attr name="ambientShadowAlpha" /> + <attr name="drawableIconSize" format="dimension" /> + <attr name="drawableIconInsetSize" format="dimension" /> + </declare-styleable> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt new file mode 100644 index 000000000000..3748eba47be5 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.shadow + +import android.graphics.BlendMode +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.RenderEffect +import android.graphics.RenderNode +import android.graphics.Shader +import android.graphics.drawable.Drawable +import android.graphics.drawable.InsetDrawable +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo + +/** A component to draw an icon with two layers of shadows. */ +class DoubleShadowIconDrawable( + keyShadowInfo: ShadowInfo, + ambientShadowInfo: ShadowInfo, + iconDrawable: Drawable, + iconSize: Int, + val iconInsetSize: Int +) : Drawable() { + private val mAmbientShadowInfo: ShadowInfo + private val mCanvasSize: Int + private val mKeyShadowInfo: ShadowInfo + private val mIconDrawable: InsetDrawable + private val mDoubleShadowNode: RenderNode + + init { + mCanvasSize = iconSize + iconInsetSize * 2 + mKeyShadowInfo = keyShadowInfo + mAmbientShadowInfo = ambientShadowInfo + setBounds(0, 0, mCanvasSize, mCanvasSize) + mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize) + mIconDrawable.setBounds(0, 0, mCanvasSize, mCanvasSize) + mDoubleShadowNode = createShadowRenderNode() + } + + private fun createShadowRenderNode(): RenderNode { + val renderNode = RenderNode("DoubleShadowNode") + renderNode.setPosition(0, 0, mCanvasSize, mCanvasSize) + // Create render effects + val ambientShadow = + createShadowRenderEffect( + mAmbientShadowInfo.blur, + mAmbientShadowInfo.offsetX, + mAmbientShadowInfo.offsetY, + mAmbientShadowInfo.alpha + ) + val keyShadow = + createShadowRenderEffect( + mKeyShadowInfo.blur, + mKeyShadowInfo.offsetX, + mKeyShadowInfo.offsetY, + mKeyShadowInfo.alpha + ) + val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN) + renderNode.setRenderEffect(blend) + return renderNode + } + + private fun createShadowRenderEffect( + radius: Float, + offsetX: Float, + offsetY: Float, + alpha: Float + ): RenderEffect { + return RenderEffect.createColorFilterEffect( + PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY), + RenderEffect.createOffsetEffect( + offsetX, + offsetY, + RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP) + ) + ) + } + + override fun draw(canvas: Canvas) { + if (canvas.isHardwareAccelerated) { + if (!mDoubleShadowNode.hasDisplayList()) { + // Record render node if its display list is not recorded or discarded + // (which happens when it's no longer drawn by anything). + val recordingCanvas = mDoubleShadowNode.beginRecording() + mIconDrawable.draw(recordingCanvas) + mDoubleShadowNode.endRecording() + } + canvas.drawRenderNode(mDoubleShadowNode) + } + mIconDrawable.draw(canvas) + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSPARENT + } + + override fun setAlpha(alpha: Int) { + mIconDrawable.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + mIconDrawable.colorFilter = colorFilter + } + + override fun setTint(color: Int) { + mIconDrawable.setTint(color) + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt new file mode 100644 index 000000000000..f2db129120e9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.shadow + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.widget.TextClock +import com.android.systemui.shared.R +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows + +/** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */ +class DoubleShadowTextClock +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : TextClock(context, attrs, defStyleAttr, defStyleRes) { + private val mAmbientShadowInfo: ShadowInfo + private val mKeyShadowInfo: ShadowInfo + + init { + val attributes = + context.obtainStyledAttributes( + attrs, + R.styleable.DoubleShadowTextClock, + defStyleAttr, + defStyleRes + ) + try { + val keyShadowBlur = + attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0) + val keyShadowOffsetX = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextClock_keyShadowOffsetX, + 0 + ) + val keyShadowOffsetY = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextClock_keyShadowOffsetY, + 0 + ) + val keyShadowAlpha = + attributes.getFloat(R.styleable.DoubleShadowTextClock_keyShadowAlpha, 0f) + mKeyShadowInfo = + ShadowInfo( + keyShadowBlur.toFloat(), + keyShadowOffsetX.toFloat(), + keyShadowOffsetY.toFloat(), + keyShadowAlpha + ) + val ambientShadowBlur = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextClock_ambientShadowBlur, + 0 + ) + val ambientShadowOffsetX = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextClock_ambientShadowOffsetX, + 0 + ) + val ambientShadowOffsetY = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextClock_ambientShadowOffsetY, + 0 + ) + val ambientShadowAlpha = + attributes.getFloat(R.styleable.DoubleShadowTextClock_ambientShadowAlpha, 0f) + mAmbientShadowInfo = + ShadowInfo( + ambientShadowBlur.toFloat(), + ambientShadowOffsetX.toFloat(), + ambientShadowOffsetY.toFloat(), + ambientShadowAlpha + ) + } finally { + attributes.recycle() + } + } + + public override fun onDraw(canvas: Canvas) { + applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt index b1dc5a2e5dea..eaac93dc2555 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt @@ -14,31 +14,33 @@ * limitations under the License. */ -package com.android.systemui.dreams.complication +package com.android.systemui.shared.shadow import android.graphics.Canvas +import android.graphics.Color import android.widget.TextView -import androidx.annotation.ColorInt -class DoubleShadowTextHelper -constructor( - private val keyShadowInfo: ShadowInfo, - private val ambientShadowInfo: ShadowInfo, -) { +object DoubleShadowTextHelper { data class ShadowInfo( val blur: Float, val offsetX: Float = 0f, val offsetY: Float = 0f, - @ColorInt val color: Int + val alpha: Float ) - fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) { + fun applyShadows( + keyShadowInfo: ShadowInfo, + ambientShadowInfo: ShadowInfo, + view: TextView, + canvas: Canvas, + onDrawCallback: () -> Unit + ) { // We enhance the shadow by drawing the shadow twice view.paint.setShadowLayer( ambientShadowInfo.blur, ambientShadowInfo.offsetX, ambientShadowInfo.offsetY, - ambientShadowInfo.color + Color.argb(ambientShadowInfo.alpha, 0f, 0f, 0f) ) onDrawCallback() canvas.save() @@ -53,7 +55,7 @@ constructor( keyShadowInfo.blur, keyShadowInfo.offsetX, keyShadowInfo.offsetY, - keyShadowInfo.color + Color.argb(keyShadowInfo.alpha, 0f, 0f, 0f) ) onDrawCallback() canvas.restore() diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt new file mode 100644 index 000000000000..25d272185bc0 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.shadow + +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.widget.TextView +import com.android.systemui.shared.R +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows + +/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */ +class DoubleShadowTextView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : TextView(context, attrs, defStyleAttr, defStyleRes) { + private val mKeyShadowInfo: ShadowInfo + private val mAmbientShadowInfo: ShadowInfo + + init { + val attributes = + context.obtainStyledAttributes( + attrs, + R.styleable.DoubleShadowTextView, + defStyleAttr, + defStyleRes + ) + val drawableSize: Int + val drawableInsetSize: Int + try { + val keyShadowBlur = + attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0) + val keyShadowOffsetX = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_keyShadowOffsetX, + 0 + ) + val keyShadowOffsetY = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_keyShadowOffsetY, + 0 + ) + val keyShadowAlpha = + attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f) + mKeyShadowInfo = + ShadowInfo( + keyShadowBlur.toFloat(), + keyShadowOffsetX.toFloat(), + keyShadowOffsetY.toFloat(), + keyShadowAlpha + ) + val ambientShadowBlur = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_ambientShadowBlur, + 0 + ) + val ambientShadowOffsetX = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_ambientShadowOffsetX, + 0 + ) + val ambientShadowOffsetY = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_ambientShadowOffsetY, + 0 + ) + val ambientShadowAlpha = + attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f) + mAmbientShadowInfo = + ShadowInfo( + ambientShadowBlur.toFloat(), + ambientShadowOffsetX.toFloat(), + ambientShadowOffsetY.toFloat(), + ambientShadowAlpha + ) + drawableSize = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_drawableIconSize, + 0 + ) + drawableInsetSize = + attributes.getDimensionPixelSize( + R.styleable.DoubleShadowTextView_drawableIconInsetSize, + 0 + ) + } finally { + attributes.recycle() + } + + val drawables = arrayOf<Drawable?>(null, null, null, null) + for ((index, drawable) in compoundDrawablesRelative.withIndex()) { + if (drawable == null) continue + drawables[index] = + DoubleShadowIconDrawable( + mKeyShadowInfo, + mAmbientShadowInfo, + drawable, + drawableSize, + drawableInsetSize + ) + } + setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]) + } + + public override fun onDraw(canvas: Canvas) { + applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index f0210fd4c914..5d6598d63a1b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -49,6 +49,8 @@ public final class InteractionJankMonitorWrapper { InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET; public static final int CUJ_SPLIT_SCREEN_ENTER = InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER; + public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = + InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; @IntDef({ CUJ_APP_LAUNCH_FROM_RECENTS, @@ -57,6 +59,7 @@ public final class InteractionJankMonitorWrapper { CUJ_APP_CLOSE_TO_PIP, CUJ_QUICK_SWITCH, CUJ_APP_LAUNCH_FROM_WIDGET, + CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 97e024238778..6d12485fd6b5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -47,6 +47,8 @@ public class QuickStepContract { public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip"; // See ISplitScreen.aidl public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen"; + // See IFloatingTasks.aidl + public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks"; // See IOneHanded.aidl public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed"; // See IShellTransitions.aidl diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index f82e7dbd5d12..71470e8870de 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -52,17 +52,15 @@ data class KeyguardFaceListenModel( val becauseCannotSkipBouncer: Boolean, val biometricSettingEnabledForUser: Boolean, val bouncerFullyShown: Boolean, - val bouncerIsOrWillShow: Boolean, val faceAuthenticated: Boolean, val faceDisabled: Boolean, val faceLockedOut: Boolean, val fpLockedOut: Boolean, val goingToSleep: Boolean, - val keyguardAwakeExcludingBouncerShowing: Boolean, + val keyguardAwake: Boolean, val keyguardGoingAway: Boolean, val listeningForFaceAssistant: Boolean, val occludingAppRequestingFaceAuth: Boolean, - val onlyFaceEnrolled: Boolean, val primaryUser: Boolean, val scanningAllowedByStrongAuth: Boolean, val secureCameraLaunched: Boolean, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 2cc5ccdc3fa1..1e5c53de4446 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -24,6 +24,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; import android.animation.Animator; @@ -106,6 +107,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_password; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 987164557a7a..5b223242670c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -330,6 +330,9 @@ public class KeyguardPatternViewController case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); break; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; case PROMPT_REASON_NONE: break; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c46e33d9fd53..0a91150e6c39 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -22,6 +22,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; import android.animation.Animator; @@ -123,6 +124,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_pin; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f73c98e4971b..2bdb1b894c4a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -22,8 +22,6 @@ import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; import static java.lang.Integer.max; @@ -87,8 +85,8 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter; import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; @@ -1098,6 +1096,7 @@ public class KeyguardSecurityContainer extends FrameLayout { return; } + mView.setAlpha(1f); mUserSwitcherViewGroup.setAlpha(0f); ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, 1f); @@ -1137,7 +1136,7 @@ public class KeyguardSecurityContainer extends FrameLayout { KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); - BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) { + BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { UserRecord item = getItem(position); @@ -1172,8 +1171,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } textView.setSelected(item == currentUser); view.setEnabled(item.isSwitchToEnabled); - view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : - USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(view); return view; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index ac00e9453c97..9d0a8acf02b4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -61,6 +61,12 @@ public interface KeyguardSecurityView { int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7; /** + * Some auth is required because the trustagent expired either from timeout or manually by + * the user + */ + int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8; + + /** * Reset the view and prepare to take input. This should do things like clearing the * password or pattern and clear error messages. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index d8cffd7984ba..5995e859c786 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -107,6 +107,14 @@ public class KeyguardSimPukViewController } @Override + public void onResume(int reason) { + super.onResume(reason); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + } + + @Override void resetState() { super.resetState(); mStateMachine.reset(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 7e04f042e487..f259a542908c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -2579,11 +2579,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED; - // mKeyguardIsVisible is true even when the bouncer is shown, we don't want to run face auth - // on bouncer if both fp and fingerprint are enrolled. - final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible - && mDeviceInteractive && !mGoingToSleep - && !statusBarShadeLocked && !mBouncerIsOrWillBeShowing; + final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep + && !statusBarShadeLocked; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); final boolean isLockDown = @@ -2623,16 +2620,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean faceDisabledForUser = isFaceDisabled(user); final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); - final boolean onlyFaceEnrolled = isOnlyFaceEnrolled(); final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. final boolean shouldListen = - ((mBouncerFullyShown && !mGoingToSleep && onlyFaceEnrolled) + (mBouncerFullyShown && !mGoingToSleep || mAuthInterruptActive || mOccludingAppRequestingFace - || awakeKeyguardExcludingBouncerShowing + || awakeKeyguard || shouldListenForFaceAssistant || mAuthController.isUdfpsFingerDown() || mUdfpsBouncerShowing) @@ -2653,17 +2649,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab becauseCannotSkipBouncer, biometricEnabledForUser, mBouncerFullyShown, - mBouncerIsOrWillBeShowing, faceAuthenticated, faceDisabledForUser, isFaceLockedOut(), fpLockedout, mGoingToSleep, - awakeKeyguardExcludingBouncerShowing, + awakeKeyguard, mKeyguardGoingAway, shouldListenForFaceAssistant, mOccludingAppRequestingFace, - onlyFaceEnrolled, mIsPrimaryUser, strongAuthAllowsScanning, mSecureCameraLaunched, @@ -2673,11 +2667,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return shouldListen; } - private boolean isOnlyFaceEnrolled() { - return isFaceEnrolled() - && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser); - } - private void maybeLogListenerModelData(KeyguardListenModel model) { mLogger.logKeyguardListenerModel(model); @@ -3229,8 +3218,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing); } } - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } if (wasBouncerFullyShown != mBouncerFullyShown) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 7fc81231c90b..a5fdc68226e8 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -103,7 +103,6 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -253,7 +252,6 @@ public class Dependency { @Inject Lazy<UserInfoController> mUserInfoController; @Inject Lazy<KeyguardStateController> mKeyguardMonitor; @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; - @Inject Lazy<BatteryController> mBatteryController; @Inject Lazy<NightDisplayListener> mNightDisplayListener; @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; @Inject Lazy<ManagedProfileController> mManagedProfileController; @@ -404,8 +402,6 @@ public class Dependency { mProviders.put(UserInfoController.class, mUserInfoController::get); - mProviders.put(BatteryController.class, mBatteryController::get); - mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 2e13903814a5..67b683ec643a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -455,7 +455,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - boolean needToUpdateProviderViews = false; final String newUniqueId = mDisplayInfo.uniqueId; if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; @@ -473,37 +472,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab setupDecorations(); return; } - - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerDrawable(); - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - final float newRatio = getPhysicalPixelDisplaySizeRatio(); - if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) { - mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio); - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - if (needToUpdateProviderViews) { - updateOverlayProviderViews(null); - } else { - updateOverlayProviderViews(new Integer[] { - mFaceScanningViewId, - R.id.display_cutout, - R.id.display_cutout_left, - R.id.display_cutout_right, - R.id.display_cutout_bottom, - }); - } - - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.onDisplayChanged(newUniqueId); } } }; @@ -1069,6 +1037,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { mRotation = newRotation; mDisplayMode = newMod; + mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( + getPhysicalPixelDisplaySizeRatio()); if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingConfigChange = false; mScreenDecorHwcLayer.updateRotation(mRotation); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 5f586c927ef7..50c38e54b4d0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -96,7 +96,8 @@ public abstract class SystemUIInitializer { .setStartingSurface(mWMComponent.getStartingSurface()) .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setBackAnimation(mWMComponent.getBackAnimation()); + .setBackAnimation(mWMComponent.getBackAnimation()) + .setFloatingTasks(mWMComponent.getFloatingTasks()); // Only initialize when not starting from tests since this currently initializes some // components that shouldn't be run in the test environment @@ -115,7 +116,8 @@ public abstract class SystemUIInitializer { .setDisplayAreaHelper(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) - .setBackAnimation(Optional.ofNullable(null)); + .setBackAnimation(Optional.ofNullable(null)) + .setFloatingTasks(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (initializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 5610377367c4..1ceb6b3ca172 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -187,7 +187,10 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba public void onReceive(Context context, Intent intent) { if (mCurrentDialog != null && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received"); + String reason = intent.getStringExtra("reason"); + reason = (reason != null) ? reason : "unknown"; + Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason); + mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; @@ -370,18 +373,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Override public void onTryAgainPressed(long requestId) { - if (mReceiver == null) { - Log.e(TAG, "onTryAgainPressed: Receiver is null"); - return; - } - - if (requestId != mCurrentDialog.getRequestId()) { - Log.w(TAG, "requestId doesn't match, skip onTryAgainPressed"); + final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); + if (receiver == null) { + Log.w(TAG, "Skip onTryAgainPressed"); return; } try { - mReceiver.onTryAgainPressed(); + receiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); } @@ -389,18 +388,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Override public void onDeviceCredentialPressed(long requestId) { - if (mReceiver == null) { - Log.e(TAG, "onDeviceCredentialPressed: Receiver is null"); - return; - } - - if (requestId != mCurrentDialog.getRequestId()) { - Log.w(TAG, "requestId doesn't match, skip onDeviceCredentialPressed"); + final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); + if (receiver == null) { + Log.w(TAG, "Skip onDeviceCredentialPressed"); return; } try { - mReceiver.onDeviceCredentialPressed(); + receiver.onDeviceCredentialPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling credential button", e); } @@ -408,18 +403,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Override public void onSystemEvent(int event, long requestId) { - if (mReceiver == null) { - Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null"); - return; - } - - if (requestId != mCurrentDialog.getRequestId()) { - Log.w(TAG, "requestId doesn't match, skip onSystemEvent"); + final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); + if (receiver == null) { + Log.w(TAG, "Skip onSystemEvent"); return; } try { - mReceiver.onSystemEvent(event); + receiver.onSystemEvent(event); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending system event", e); } @@ -427,23 +418,46 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Override public void onDialogAnimatedIn(long requestId) { - if (mReceiver == null) { - Log.e(TAG, "onDialogAnimatedIn: Receiver is null"); - return; - } - - if (requestId != mCurrentDialog.getRequestId()) { - Log.w(TAG, "requestId doesn't match, skip onDialogAnimatedIn"); + final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); + if (receiver == null) { + Log.w(TAG, "Skip onDialogAnimatedIn"); return; } try { - mReceiver.onDialogAnimatedIn(); + receiver.onDialogAnimatedIn(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); } } + @Nullable + private IBiometricSysuiReceiver getCurrentReceiver(long requestId) { + if (!isRequestIdValid(requestId)) { + return null; + } + + if (mReceiver == null) { + Log.w(TAG, "getCurrentReceiver: Receiver is null"); + } + + return mReceiver; + } + + private boolean isRequestIdValid(long requestId) { + if (mCurrentDialog == null) { + Log.w(TAG, "shouldNotifyReceiver: dialog already gone"); + return false; + } + + if (requestId != mCurrentDialog.getRequestId()) { + Log.w(TAG, "shouldNotifyReceiver: requestId doesn't match"); + return false; + } + + return true; + } + @Override public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation, long requestId) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 36287f59d746..27e9af9b394a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -342,8 +342,11 @@ public class UdfpsController implements DozeReceiver { if (mOverlay != null && mOverlay.getRequestReason() != REASON_AUTH_KEYGUARD && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: " - + mOverlay.getRequestReason()); + String reason = intent.getStringExtra("reason"); + reason = (reason != null) ? reason : "unknown"; + Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason + + ", mRequestReason: " + mOverlay.getRequestReason()); + mOverlay.cancel(); hideUdfpsOverlay(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index bfbf37a5d3e9..d53e56f7b852 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -220,7 +220,7 @@ public class BrightLineFalsingManager implements FalsingManager { return r; }).collect(Collectors.toList()); - logDebug("False Gesture: " + localResult[0]); + logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]); return localResult[0]; } @@ -454,6 +454,12 @@ public class BrightLineFalsingManager implements FalsingManager { } } + static void logVerbose(String msg) { + if (DEBUG) { + Log.v(TAG, msg); + } + } + static void logInfo(String msg) { Log.i(TAG, msg); RECENT_INFO_LOG.add(msg); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java index f18413be0134..c2922968b58e 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java @@ -42,8 +42,9 @@ public abstract class Classifier { public static final int QS_COLLAPSE = 12; public static final int UDFPS_AUTHENTICATION = 13; public static final int LOCK_ICON = 14; - public static final int QS_SWIPE = 15; + public static final int QS_SWIPE_SIDE = 15; public static final int BACK_GESTURE = 16; + public static final int QS_SWIPE_NESTED = 17; @IntDef({ QUICK_SETTINGS, @@ -62,7 +63,8 @@ public abstract class Classifier { BRIGHTNESS_SLIDER, UDFPS_AUTHENTICATION, LOCK_ICON, - QS_SWIPE, + QS_SWIPE_SIDE, + QS_SWIPE_NESTED, BACK_GESTURE }) @Retention(RetentionPolicy.SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java index d0fe1c37d4fa..5e4f149d3ca3 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java @@ -24,6 +24,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.provider.DeviceConfig; @@ -156,7 +157,8 @@ class DistanceClassifier extends FalsingClassifier { || interactionType == QS_COLLAPSE || interactionType == Classifier.UDFPS_AUTHENTICATION || interactionType == Classifier.LOCK_ICON - || interactionType == Classifier.QS_SWIPE) { + || interactionType == Classifier.QS_SWIPE_SIDE + || interactionType == QS_SWIPE_NESTED) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java index d75752841023..d18d62f025fe 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java @@ -148,6 +148,11 @@ public abstract class FalsingClassifier { } /** */ + public static void logVerbose(String msg) { + BrightLineFalsingManager.logVerbose(msg); + } + + /** */ public static void logInfo(String msg) { BrightLineFalsingManager.logInfo(msg); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index a3ecb0c0b273..3991a35e958a 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -78,10 +78,10 @@ public class FalsingDataProvider { void onMotionEvent(MotionEvent motionEvent) { List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent); - FalsingClassifier.logDebug("Unpacked into: " + motionEvents.size()); + FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size()); if (BrightLineFalsingManager.DEBUG) { for (MotionEvent m : motionEvents) { - FalsingClassifier.logDebug( + FalsingClassifier.logVerbose( "x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime()); } } @@ -92,7 +92,7 @@ public class FalsingDataProvider { } mRecentMotionEvents.addAll(motionEvents); - FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size()); + FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size()); mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent)); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java index 32d9ca59951d..07f94e792a93 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java @@ -19,7 +19,7 @@ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; -import static com.android.systemui.classifier.Classifier.QS_SWIPE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import android.provider.DeviceConfig; @@ -119,7 +119,7 @@ class ProximityClassifier extends FalsingClassifier { @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER - || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE) { + || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java index f040712706cd..776bc88ad6bf 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java @@ -24,7 +24,8 @@ import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN; import static com.android.systemui.classifier.Classifier.PULSE_EXPAND; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; -import static com.android.systemui.classifier.Classifier.QS_SWIPE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; @@ -86,9 +87,12 @@ public class TypeClassifier extends FalsingClassifier { case QS_COLLAPSE: wrongDirection = !vertical || !up; break; - case QS_SWIPE: + case QS_SWIPE_SIDE: wrongDirection = vertical; break; + case QS_SWIPE_NESTED: + wrongDirection = !vertical; + break; default: wrongDirection = true; break; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java index 40c28fab51b8..de2bdf7ded75 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java @@ -137,8 +137,8 @@ class ZigZagClassifier extends FalsingClassifier { runningAbsDy += Math.abs(point.y - pY); pX = point.x; pY = point.y; - logDebug("(x, y, runningAbsDx, runningAbsDy) - (" + pX + ", " + pY + ", " + runningAbsDx - + ", " + runningAbsDy + ")"); + logVerbose("(x, y, runningAbsDx, runningAbsDy) - (" + + pX + ", " + pY + ", " + runningAbsDx + ", " + runningAbsDy + ")"); } float devianceX = runningAbsDx - actualDx; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index fbfc94af683e..a99669970a34 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -35,6 +35,7 @@ import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; +import com.android.systemui.dump.DumpManager; import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.navigationbar.gestural.GestureModule; import com.android.systemui.plugins.qs.QSFactory; @@ -126,6 +127,7 @@ public abstract class ReferenceSystemUIModule { PowerManager powerManager, BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, + DumpManager dumpManager, @Main Handler mainHandler, @Background Handler bgHandler) { BatteryController bC = new BatteryControllerImpl( @@ -134,6 +136,7 @@ public abstract class ReferenceSystemUIModule { powerManager, broadcastDispatcher, demoModeController, + dumpManager, mainHandler, bgHandler); bC.init(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 029cabb0bc0b..7e30431a30c8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -41,6 +41,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -109,6 +110,9 @@ public interface SysUIComponent { @BindsInstance Builder setBackAnimation(Optional<BackAnimation> b); + @BindsInstance + Builder setFloatingTasks(Optional<FloatingTasks> f); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 0469152de776..443d2774f0e0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -41,6 +41,7 @@ import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FlagsModule; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.keyguard.data.BouncerViewModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.media.dagger.MediaProjectionModule; import com.android.systemui.model.SysUiState; @@ -116,6 +117,7 @@ import dagger.Provides; AppOpsModule.class, AssistModule.class, BiometricsModule.class, + BouncerViewModule.class, ClockModule.class, CoroutinesModule.class, DreamModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index b6923a867507..dd115490abfa 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -23,8 +23,6 @@ import androidx.annotation.Nullable; import com.android.systemui.SystemUIInitializerFactory; import com.android.systemui.tv.TvWMComponent; -import com.android.wm.shell.sysui.ShellCommandHandler; -import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; @@ -33,6 +31,7 @@ import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -110,4 +109,7 @@ public interface WMComponent { @WMSingleton Optional<BackAnimation> getBackAnimation(); + + @WMSingleton + Optional<FloatingTasks> getFloatingTasks(); } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index a25286438387..8b4aeefb6ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -78,23 +78,18 @@ class RoundedCornerResDelegate( reloadMeasures() } - private fun reloadAll(newReloadToken: Int) { - if (reloadToken == newReloadToken) { - return - } - reloadToken = newReloadToken - reloadRes() - reloadMeasures() - } - fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { if (displayUniqueId != newDisplayUniqueId) { displayUniqueId = newDisplayUniqueId newReloadToken ?.let { reloadToken = it } reloadRes() reloadMeasures() - } else { - newReloadToken?.let { reloadAll(it) } + } else if (newReloadToken != null) { + if (reloadToken == newReloadToken) { + return + } + reloadToken = newReloadToken + reloadMeasures() } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index d7b7777559da..733a80dd7f69 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -35,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.ComplicationHostViewController; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -73,6 +74,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). private final Handler mHandler; private final int mDreamOverlayMaxTranslationY; + private final BouncerCallbackInteractor mBouncerCallbackInteractor; private long mJitterStartTimeMillis; @@ -131,7 +133,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long burnInProtectionUpdateInterval, - @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) { + @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, + BouncerCallbackInteractor bouncerCallbackInteractor) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; @@ -151,6 +154,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mMaxBurnInOffset = maxBurnInOffset; mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; mMillisUntilFullJitter = millisUntilFullJitter; + mBouncerCallbackInteractor = bouncerCallbackInteractor; } @Override @@ -167,6 +171,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve if (bouncer != null) { bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback); } + mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); } @Override @@ -176,6 +181,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve if (bouncer != null) { bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback); } + mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); } View getContainerView() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 96f77b3654c5..696fc7254308 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -16,6 +16,7 @@ package com.android.systemui.dreams; +import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.util.Log; @@ -26,11 +27,13 @@ import android.view.WindowInsets; import android.view.WindowManager; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleRegistry; import androidx.lifecycle.ViewModelStore; +import com.android.dream.lowlight.dagger.LowLightDreamModule; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; @@ -44,6 +47,7 @@ import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor; import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Named; /** * The {@link DreamOverlayService} is responsible for placing an overlay on top of a dream. The @@ -62,6 +66,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // content area). private final DreamOverlayContainerViewController mDreamOverlayContainerViewController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Nullable + private final ComponentName mLowLightDreamComponent; private final UiEventLogger mUiEventLogger; // A reference to the {@link Window} used to hold the dream overlay. @@ -125,10 +131,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ DreamOverlayComponent.Factory dreamOverlayComponentFactory, DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT) + ComponentName lowLightDreamComponent) { mContext = context; mExecutor = executor; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLowLightDreamComponent = lowLightDreamComponent; mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); mStateController = stateController; mUiEventLogger = uiEventLogger; @@ -155,6 +164,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ windowManager.removeView(mWindow.getDecorView()); } mStateController.setOverlayActive(false); + mStateController.setLowLightActive(false); mDestroyed = true; super.onDestroy(); } @@ -163,6 +173,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); setCurrentState(Lifecycle.State.STARTED); + final ComponentName dreamComponent = getDreamComponent(); + mStateController.setLowLightActive( + dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent)); mExecutor.execute(() -> { if (mDestroyed) { // The task could still be executed after the service has been destroyed. Bail if diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index 69e41ba9b284..72feaca59ace 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -50,6 +50,7 @@ public class DreamOverlayStateController implements private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0; + public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1; private static final int OP_CLEAR_STATE = 1; private static final int OP_SET_STATE = 2; @@ -193,6 +194,14 @@ public class DreamOverlayStateController implements return containsState(STATE_DREAM_OVERLAY_ACTIVE); } + /** + * Returns whether low light mode is active. + * @return {@code true} if in low light mode, {@code false} otherwise. + */ + public boolean isLowLightActive() { + return containsState(STATE_LOW_LIGHT_ACTIVE); + } + private boolean containsState(int state) { return (mState & state) != 0; } @@ -222,6 +231,14 @@ public class DreamOverlayStateController implements } /** + * Sets whether low light mode is active. + * @param active {@code true} if low light mode is active, {@code false} otherwise. + */ + public void setLowLightActive(boolean active) { + modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE); + } + + /** * Returns the available complication types. */ @Complication.ComplicationType diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index aa59cc666caa..bb1c4303041a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -20,7 +20,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_HIDING; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import android.annotation.Nullable; import android.app.AlarmManager; import android.app.StatusBarManager; import android.content.res.Resources; @@ -36,6 +35,8 @@ import android.text.format.DateFormat; import android.util.PluralsMessageFormatter; import android.view.View; +import androidx.annotation.Nullable; + import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; @@ -73,6 +74,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private final Optional<DreamOverlayNotificationCountProvider> mDreamOverlayNotificationCountProvider; private final ZenModeController mZenModeController; + private final DreamOverlayStateController mDreamOverlayStateController; + private final StatusBarWindowStateController mStatusBarWindowStateController; private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider; private final Executor mMainExecutor; private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems = @@ -102,6 +105,14 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } }; + private final DreamOverlayStateController.Callback mDreamOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + updateLowLightState(); + } + }; + private final IndividualSensorPrivacyController.Callback mSensorCallback = (sensor, blocked) -> updateMicCameraBlockedStatusIcon(); @@ -140,7 +151,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider, ZenModeController zenModeController, StatusBarWindowStateController statusBarWindowStateController, - DreamOverlayStatusBarItemsProvider statusBarItemsProvider) { + DreamOverlayStatusBarItemsProvider statusBarItemsProvider, + DreamOverlayStateController dreamOverlayStateController) { super(view); mResources = resources; mMainExecutor = mainExecutor; @@ -151,8 +163,10 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDateFormatUtil = dateFormatUtil; mSensorPrivacyController = sensorPrivacyController; mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider; + mStatusBarWindowStateController = statusBarWindowStateController; mStatusBarItemsProvider = statusBarItemsProvider; mZenModeController = zenModeController; + mDreamOverlayStateController = dreamOverlayStateController; // Register to receive show/hide updates for the system status bar. Our custom status bar // needs to hide when the system status bar is showing to ovoid overlapping status bars. @@ -180,6 +194,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); + mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); + updateLowLightState(); + mTouchInsetSession.addViewToTracking(mView); } @@ -193,6 +210,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve provider -> provider.removeCallback(mNotificationCountCallback)); mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); mView.removeAllExtraStatusBarItemViews(); + mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback); mTouchInsetSession.clear(); mIsAttached = false; @@ -217,6 +235,15 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve hasAlarm ? buildAlarmContentDescription(alarm) : null); } + private void updateLowLightState() { + int visibility = View.VISIBLE; + if (mDreamOverlayStateController.isLowLightActive() + || mStatusBarWindowStateController.windowIsShowing()) { + visibility = View.INVISIBLE; + } + mView.setVisibility(visibility); + } + private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) { final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma"; final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); @@ -272,7 +299,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) { mMainExecutor.execute(() -> { - if (!mIsAttached) { + if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java deleted file mode 100644 index 789ebc517271..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.complication; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.TextClock; - -import com.android.systemui.R; -import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo; - -import kotlin.Unit; - -/** - * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows) - */ -public class DoubleShadowTextClock extends TextClock { - private final DoubleShadowTextHelper mShadowHelper; - - public DoubleShadowTextClock(Context context) { - this(context, null); - } - - public DoubleShadowTextClock(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final Resources resources = context.getResources(); - final ShadowInfo keyShadowInfo = new ShadowInfo( - resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius), - resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx), - resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy), - resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color)); - - final ShadowInfo ambientShadowInfo = new ShadowInfo( - resources.getDimensionPixelSize( - R.dimen.dream_overlay_clock_ambient_text_shadow_radius), - resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx), - resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy), - resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color)); - mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); - } - - @Override - public void onDraw(Canvas canvas) { - mShadowHelper.applyShadows(this, canvas, () -> { - super.onDraw(canvas); - return Unit.INSTANCE; - }); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java deleted file mode 100644 index cf7e3127dedf..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.complication; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.systemui.R; - -import kotlin.Unit; - -/** - * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows} - */ -public class DoubleShadowTextView extends TextView { - private final DoubleShadowTextHelper mShadowHelper; - - public DoubleShadowTextView(Context context) { - this(context, null); - } - - public DoubleShadowTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final Resources resources = context.getResources(); - final DoubleShadowTextHelper.ShadowInfo - keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo( - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_key_text_shadow_radius), - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_key_text_shadow_dx), - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_key_text_shadow_dy), - resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color)); - - final DoubleShadowTextHelper.ShadowInfo - ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo( - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius), - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx), - resources.getDimensionPixelSize( - R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy), - resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color)); - mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); - } - - @Override - public void onDraw(Canvas canvas) { - mShadowHelper.applyShadows(this, canvas, () -> { - super.onDraw(canvas); - return Unit.INSTANCE; - }); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java index 21a51d1096d5..c07d4022df76 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java @@ -18,13 +18,21 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; +import android.app.PendingIntent; import android.util.Log; import android.view.View; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -87,6 +95,15 @@ public class DreamMediaEntryComplication implements Complication { private final DreamOverlayStateController mDreamOverlayStateController; private final MediaDreamComplication mMediaComplication; + private final MediaCarouselController mMediaCarouselController; + + private final ActivityStarter mActivityStarter; + private final ActivityIntentHelper mActivityIntentHelper; + private final KeyguardStateController mKeyguardStateController; + private final NotificationLockscreenUserManager mLockscreenUserManager; + + private final FeatureFlags mFeatureFlags; + private boolean mIsTapToOpenEnabled; private boolean mMediaComplicationAdded; @@ -94,15 +111,28 @@ public class DreamMediaEntryComplication implements Complication { DreamMediaEntryViewController( @Named(DREAM_MEDIA_ENTRY_VIEW) View view, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication mediaComplication) { + MediaDreamComplication mediaComplication, + MediaCarouselController mediaCarouselController, + ActivityStarter activityStarter, + ActivityIntentHelper activityIntentHelper, + KeyguardStateController keyguardStateController, + NotificationLockscreenUserManager lockscreenUserManager, + FeatureFlags featureFlags) { super(view); mDreamOverlayStateController = dreamOverlayStateController; mMediaComplication = mediaComplication; + mMediaCarouselController = mediaCarouselController; + mActivityStarter = activityStarter; + mActivityIntentHelper = activityIntentHelper; + mKeyguardStateController = keyguardStateController; + mLockscreenUserManager = lockscreenUserManager; + mFeatureFlags = featureFlags; mView.setOnClickListener(this::onClickMediaEntry); } @Override protected void onViewAttached() { + mIsTapToOpenEnabled = mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN); } @Override @@ -113,6 +143,31 @@ public class DreamMediaEntryComplication implements Complication { private void onClickMediaEntry(View v) { if (DEBUG) Log.d(TAG, "media entry complication tapped"); + if (mIsTapToOpenEnabled) { + final PendingIntent clickIntent = + mMediaCarouselController.getCurrentVisibleMediaContentIntent(); + + if (clickIntent == null) { + return; + } + + // See StatusBarNotificationActivityStarter#onNotificationClicked + final boolean showOverLockscreen = mKeyguardStateController.isShowing() + && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + + if (showOverLockscreen) { + mActivityStarter.startActivity(clickIntent.getIntent(), + /* dismissShade */ true, + /* animationController */ null, + /* showOverLockscreenWhenLocked */ true); + } else { + mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, null); + } + + return; + } + if (!mMediaComplicationAdded) { addMediaComplication(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 2dd2098a78b9..f9dca08ae14f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -19,6 +19,7 @@ package com.android.systemui.dreams.dagger; import android.content.Context; import android.content.res.Resources; +import com.android.dream.lowlight.dagger.LowLightDreamModule; import com.android.settingslib.dream.DreamBackend; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -37,6 +38,7 @@ import dagger.Provides; */ @Module(includes = { RegisteredComplicationsModule.class, + LowLightDreamModule.class, }, subcomponents = { DreamOverlayComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 2540035ab793..54c1b28bb106 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -104,6 +104,10 @@ public class Flags { public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY = new UnreleasedFlag(209, true); + /** Whether the new implementation of UserSwitcherController should be used. */ + public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER = + new UnreleasedFlag(210, false); + /***************************************/ // 300 - power menu public static final ReleasedFlag POWER_MENU_LITE = @@ -192,11 +196,12 @@ public class Flags { /***************************************/ // 900 - media - public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900); + public static final UnreleasedFlag MEDIA_TAP_TO_TRANSFER = new UnreleasedFlag(900); public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901); public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903); public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); - public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905); + public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = @@ -234,6 +239,14 @@ public class Flags { public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL = new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false); + @Keep + public static final SysPropBooleanFlag FLOATING_TASKS_ENABLED = + new SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false); + + @Keep + public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES = + new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false); + // 1200 - predictive back @Keep public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 26db3ee4926f..6a7c3901551c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -22,8 +22,10 @@ import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -802,6 +804,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; + } else if (trustAgentsEnabled + && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) { + return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 || mUpdateMonitor.isFingerprintLockedOut())) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; @@ -841,6 +846,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, if (launchIsFullScreen) { mCentralSurfaces.instantCollapseNotificationPanel(); } + + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); } @NonNull @@ -987,6 +994,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */); Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: " + mOccluded); + + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION); } @Override @@ -995,6 +1004,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false."); + mInteractionJankMonitor.begin( + createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION) + .setTag("UNOCCLUDE")); setOccluded(false /* isOccluded */, true /* animate */); if (apps == null || apps.length == 0 || apps[0] == null) { @@ -1053,6 +1065,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, try { finishedCallback.onAnimationFinished(); mUnoccludeAnimator = null; + + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); } catch (RemoteException e) { e.printStackTrace(); } @@ -2563,7 +2577,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, }; try { mInteractionJankMonitor.begin( - createInteractionJankMonitorConf("RunRemoteAnimation")); + createInteractionJankMonitorConf( + CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RunRemoteAnimation")); runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps, wallpapers, nonApps, callback); } catch (RemoteException e) { @@ -2578,7 +2593,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSurfaceBehindRemoteAnimationRunning = true; mInteractionJankMonitor.begin( - createInteractionJankMonitorConf("DismissPanel")); + createInteractionJankMonitorConf( + CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "DismissPanel")); // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get() @@ -2586,7 +2602,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, apps, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( - createInteractionJankMonitorConf("RemoteAnimationDisabled")); + createInteractionJankMonitorConf( + CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled")); mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); @@ -2666,10 +2683,15 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, sendUserPresentBroadcast(); } - private Configuration.Builder createInteractionJankMonitorConf(String tag) { - return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION, - mKeyguardViewControllerLazy.get().getViewRootImpl().getView()) - .setTag(tag); + private Configuration.Builder createInteractionJankMonitorConf(int cuj) { + return createInteractionJankMonitorConf(cuj, null /* tag */); + } + + private Configuration.Builder createInteractionJankMonitorConf(int cuj, @Nullable String tag) { + final Configuration.Builder builder = Configuration.Builder.withView( + cuj, mKeyguardViewControllerLazy.get().getViewRootImpl().getView()); + + return tag != null ? builder.setTag(tag) : builder; } /** @@ -3280,6 +3302,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); + mInteractionJankMonitor.begin( + createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION) + .setTag("OCCLUDE")); + // This is the first signal we have from WM that we're going to be occluded. Set our // internal state to reflect that immediately, vs. waiting for the launch animator to // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to @@ -3296,6 +3322,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, + "Setting occluded state to: " + isKeyguardOccluded); setOccluded(isKeyguardOccluded /* occluded */, false /* animate */); + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION); } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt new file mode 100644 index 000000000000..99ae85d7a548 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.data + +import android.view.KeyEvent +import com.android.systemui.dagger.SysUISingleton +import java.lang.ref.WeakReference +import javax.inject.Inject + +/** An abstraction to interface with the ui layer, without changing state. */ +interface BouncerView { + var delegate: BouncerViewDelegate? +} + +/** A lightweight class to hold reference to the ui delegate. */ +@SysUISingleton +class BouncerViewImpl @Inject constructor() : BouncerView { + private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null) + override var delegate: BouncerViewDelegate? + get() = _delegate.get() + set(value) { + _delegate = WeakReference(value) + } +} + +/** An abstraction that implements view logic. */ +interface BouncerViewDelegate { + fun isFullScreenBouncer(): Boolean + fun shouldDismissOnMenuPressed(): Boolean + fun interceptMediaKey(event: KeyEvent?): Boolean + fun dispatchBackKeyEventPreIme(): Boolean + fun showNextSecurityScreenOrFinish(): Boolean + fun resume() +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt index 24275e002c7f..390c54e0350a 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.systemui.keyguard.data -import android.app.Activity; -import android.os.Bundle; +import dagger.Binds +import dagger.Module -public class NonResizeableActivity extends Activity { - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.activity_non_resizeable); - } +@Module +interface BouncerViewModule { + /** Binds BouncerView to BouncerViewImpl and makes it injectable. */ + @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt new file mode 100644 index 000000000000..543389e0a7cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.data.repository + +import android.hardware.biometrics.BiometricSourceType +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.keyguard.ViewMediatorCallback +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Encapsulates app state for the lock screen bouncer. */ +@SysUISingleton +class KeyguardBouncerRepository +@Inject +constructor( + private val viewMediatorCallback: ViewMediatorCallback, + keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + var bouncerPromptReason: Int? = null + /** Determines if we want to instantaneously show the bouncer instead of translating. */ + private val _isScrimmed = MutableStateFlow(false) + val isScrimmed = _isScrimmed.asStateFlow() + /** Set amount of how much of the bouncer is showing on the screen */ + private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN) + val expansionAmount = _expansionAmount.asStateFlow() + private val _isVisible = MutableStateFlow(false) + val isVisible = _isVisible.asStateFlow() + private val _show = MutableStateFlow<KeyguardBouncerModel?>(null) + val show = _show.asStateFlow() + private val _showingSoon = MutableStateFlow(false) + val showingSoon = _showingSoon.asStateFlow() + private val _hide = MutableStateFlow(false) + val hide = _hide.asStateFlow() + private val _startingToHide = MutableStateFlow(false) + val startingToHide = _startingToHide.asStateFlow() + private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null) + val onDismissAction = _onDismissAction.asStateFlow() + private val _disappearAnimation = MutableStateFlow<Runnable?>(null) + val startingDisappearAnimation = _disappearAnimation.asStateFlow() + private val _keyguardPosition = MutableStateFlow(0f) + val keyguardPosition = _keyguardPosition.asStateFlow() + private val _resourceUpdateRequests = MutableStateFlow(false) + val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow() + private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null) + val showMessage = _showMessage.asStateFlow() + private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) + /** Determines if user is already unlocked */ + val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() + private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) + val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() + private val _onScreenTurnedOff = MutableStateFlow(false) + val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow() + + val bouncerErrorMessage: CharSequence? + get() = viewMediatorCallback.consumeCustomMessage() + + init { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onStrongAuthStateChanged(userId: Int) { + bouncerPromptReason = viewMediatorCallback.bouncerPromptReason + } + + override fun onLockedOutStateChanged(type: BiometricSourceType) { + if (type == BiometricSourceType.FINGERPRINT) { + bouncerPromptReason = viewMediatorCallback.bouncerPromptReason + } + } + } + + keyguardUpdateMonitor.registerCallback(callback) + } + + fun setScrimmed(isScrimmed: Boolean) { + _isScrimmed.value = isScrimmed + } + + fun setExpansion(expansion: Float) { + _expansionAmount.value = expansion + } + + fun setVisible(isVisible: Boolean) { + _isVisible.value = isVisible + } + + fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) { + _show.value = keyguardBouncerModel + } + + fun setShowingSoon(showingSoon: Boolean) { + _showingSoon.value = showingSoon + } + + fun setHide(hide: Boolean) { + _hide.value = hide + } + + fun setStartingToHide(startingToHide: Boolean) { + _startingToHide.value = startingToHide + } + + fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) { + _onDismissAction.value = bouncerCallbackActionsModel + } + + fun setStartDisappearAnimation(runnable: Runnable?) { + _disappearAnimation.value = runnable + } + + fun setKeyguardPosition(keyguardPosition: Float) { + _keyguardPosition.value = keyguardPosition + } + + fun setResourceUpdateRequests(willUpdateResources: Boolean) { + _resourceUpdateRequests.value = willUpdateResources + } + + fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) { + _showMessage.value = bouncerShowMessageModel + } + + fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) { + _keyguardAuthenticated.value = keyguardAuthenticated + } + + fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) { + _isBackButtonEnabled.value = isBackButtonEnabled + } + + fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) { + _onScreenTurnedOff.value = onScreenTurnedOff + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt new file mode 100644 index 000000000000..10c7a3774e09 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.util.ListenerSet +import javax.inject.Inject + +/** Interactor to add and remove callbacks for the bouncer. */ +@SysUISingleton +class BouncerCallbackInteractor @Inject constructor() { + private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>() + private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>() + /** Add a KeyguardResetCallback. */ + fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) { + resetCallbacks.addIfAbsent(callback) + } + + /** Remove a KeyguardResetCallback. */ + fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) { + resetCallbacks.remove(callback) + } + + /** Adds a callback to listen to bouncer expansion updates. */ + fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) { + if (!expansionCallbacks.contains(callback)) { + expansionCallbacks.add(callback) + } + } + + /** + * Removes a previously added callback. If the callback was never added, this method does + * nothing. + */ + fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) { + expansionCallbacks.remove(callback) + } + + /** Propagate fully shown to bouncer expansion callbacks. */ + fun dispatchFullyShown() { + for (callback in expansionCallbacks) { + callback.onFullyShown() + } + } + + /** Propagate starting to hide to bouncer expansion callbacks. */ + fun dispatchStartingToHide() { + for (callback in expansionCallbacks) { + callback.onStartingToHide() + } + } + + /** Propagate starting to show to bouncer expansion callbacks. */ + fun dispatchStartingToShow() { + for (callback in expansionCallbacks) { + callback.onStartingToShow() + } + } + + /** Propagate fully hidden to bouncer expansion callbacks. */ + fun dispatchFullyHidden() { + for (callback in expansionCallbacks) { + callback.onFullyHidden() + } + } + + /** Propagate expansion changes to bouncer expansion callbacks. */ + fun dispatchExpansionChanged(expansion: Float) { + for (callback in expansionCallbacks) { + callback.onExpansionChanged(expansion) + } + } + /** Propagate visibility changes to bouncer expansion callbacks. */ + fun dispatchVisibilityChanged(visibility: Int) { + for (callback in expansionCallbacks) { + callback.onVisibilityChanged(visibility == View.VISIBLE) + } + } + + /** Propagate keyguard reset. */ + fun dispatchReset() { + for (callback in resetCallbacks) { + callback.onKeyguardReset() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt new file mode 100644 index 000000000000..7d4db37c6b0f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.res.ColorStateList +import android.os.Handler +import android.os.Trace +import android.os.UserHandle +import android.os.UserManager +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.DejankUtils +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.shared.system.SysUiStatsLog +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for interacting with the lock-screen bouncer. */ +@SysUISingleton +class BouncerInteractor +@Inject +constructor( + private val repository: KeyguardBouncerRepository, + private val bouncerView: BouncerView, + @Main private val mainHandler: Handler, + private val keyguardStateController: KeyguardStateController, + private val keyguardSecurityModel: KeyguardSecurityModel, + private val callbackInteractor: BouncerCallbackInteractor, + private val falsingCollector: FalsingCollector, + private val dismissCallbackRegistry: DismissCallbackRegistry, + keyguardBypassController: KeyguardBypassController, + keyguardUpdateMonitor: KeyguardUpdateMonitor, +) { + /** Whether we want to wait for face auth. */ + private val bouncerFaceDelay = + keyguardStateController.isFaceAuthEnabled && + !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + KeyguardUpdateMonitor.getCurrentUser() + ) && + !needsFullscreenBouncer() && + !keyguardUpdateMonitor.userNeedsStrongAuth() && + !keyguardBypassController.bypassEnabled + + /** Runnable to show the bouncer. */ + val showRunnable = Runnable { + repository.setVisible(true) + repository.setShow( + KeyguardBouncerModel( + promptReason = repository.bouncerPromptReason ?: 0, + errorMessage = repository.bouncerErrorMessage, + expansionAmount = repository.expansionAmount.value + ) + ) + repository.setShowingSoon(false) + } + + val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull() + val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {} + val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull() + val hide: Flow<Unit> = repository.hide.filter { it }.map {} + val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {} + val isVisible: Flow<Boolean> = repository.isVisible + val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull() + val expansionAmount: Flow<Float> = repository.expansionAmount + val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull() + val startingDisappearAnimation: Flow<Runnable> = + repository.startingDisappearAnimation.filterNotNull() + val onDismissAction: Flow<BouncerCallbackActionsModel> = + repository.onDismissAction.filterNotNull() + val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it } + val keyguardPosition: Flow<Float> = repository.keyguardPosition + + // TODO(b/243685699): Move isScrimmed logic to data layer. + // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. + /** Show the bouncer if necessary and set the relevant states. */ + @JvmOverloads + fun show(isScrimmed: Boolean) { + // Reset some states as we show the bouncer. + repository.setShowMessage(null) + repository.setOnScreenTurnedOff(false) + repository.setKeyguardAuthenticated(null) + repository.setHide(false) + repository.setStartingToHide(false) + + val resumeBouncer = + (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer() + + if (!resumeBouncer && repository.show.value != null) { + // If bouncer is visible, the bouncer is already showing. + return + } + + val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser() + if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) { + // In split system user mode, we never unlock system user. + return + } + + Trace.beginSection("KeyguardBouncer#show") + repository.setScrimmed(isScrimmed) + if (isScrimmed) { + setExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + } + + if (resumeBouncer) { + bouncerView.delegate?.resume() + // Bouncer is showing the next security screen and we just need to prompt a resume. + return + } + if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { + // Keyguard is done. + return + } + + repository.setShowingSoon(true) + if (bouncerFaceDelay) { + mainHandler.postDelayed(showRunnable, 1200L) + } else { + DejankUtils.postAfterTraversal(showRunnable) + } + keyguardStateController.notifyBouncerShowing(true) + callbackInteractor.dispatchStartingToShow() + + Trace.endSection() + } + + /** Sets the correct bouncer states to hide the bouncer. */ + fun hide() { + Trace.beginSection("KeyguardBouncer#hide") + if (isFullyShowing()) { + SysUiStatsLog.write( + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN + ) + dismissCallbackRegistry.notifyDismissCancelled() + } + + falsingCollector.onBouncerHidden() + keyguardStateController.notifyBouncerShowing(false /* showing */) + cancelShowRunnable() + repository.setShowingSoon(false) + repository.setOnDismissAction(null) + repository.setVisible(false) + repository.setHide(true) + repository.setShow(null) + Trace.endSection() + } + + /** + * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f + * where 0f => showing and 1f => hiding + */ + fun setExpansion(expansion: Float) { + val oldExpansion = repository.expansionAmount.value + val expansionChanged = oldExpansion != expansion + if (repository.startingDisappearAnimation.value == null) { + repository.setExpansion(expansion) + } + + if ( + expansion == KeyguardBouncer.EXPANSION_VISIBLE && + oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE + ) { + falsingCollector.onBouncerShown() + callbackInteractor.dispatchFullyShown() + } else if ( + expansion == KeyguardBouncer.EXPANSION_HIDDEN && + oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN + ) { + repository.setVisible(false) + repository.setShow(null) + falsingCollector.onBouncerHidden() + DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() } + callbackInteractor.dispatchFullyHidden() + } else if ( + expansion != KeyguardBouncer.EXPANSION_VISIBLE && + oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE + ) { + callbackInteractor.dispatchStartingToHide() + repository.setStartingToHide(true) + } + if (expansionChanged) { + callbackInteractor.dispatchExpansionChanged(expansion) + } + } + + /** Set the initial keyguard message to show when bouncer is shown. */ + fun showMessage(message: String?, colorStateList: ColorStateList?) { + repository.setShowMessage(BouncerShowMessageModel(message, colorStateList)) + } + + /** + * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is + * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we + * call cancelAction. + */ + fun setDismissAction( + onDismissAction: ActivityStarter.OnDismissAction?, + cancelAction: Runnable? + ) { + repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + } + + /** Update the resources of the views. */ + fun updateResources() { + repository.setResourceUpdateRequests(true) + } + + /** Tell the bouncer that keyguard is authenticated. */ + fun notifyKeyguardAuthenticated(strongAuth: Boolean) { + repository.setKeyguardAuthenticated(strongAuth) + } + + /** Tell the bouncer the screen has turned off. */ + fun onScreenTurnedOff() { + repository.setOnScreenTurnedOff(true) + } + + /** Update the position of the bouncer when showing. */ + fun setKeyguardPosition(position: Float) { + repository.setKeyguardPosition(position) + } + + /** Notifies that the state change was handled. */ + fun notifyKeyguardAuthenticatedHandled() { + repository.setKeyguardAuthenticated(null) + } + + /** Notify that view visibility has changed. */ + fun notifyBouncerVisibilityHasChanged(visibility: Int) { + callbackInteractor.dispatchVisibilityChanged(visibility) + } + + /** Notify that the resources have been updated */ + fun notifyUpdatedResources() { + repository.setResourceUpdateRequests(false) + } + + /** Set whether back button is enabled when on the bouncer screen. */ + fun setBackButtonEnabled(enabled: Boolean) { + repository.setIsBackButtonEnabled(enabled) + } + + /** Tell the bouncer to start the pre hide animation. */ + fun startDisappearAnimation(runnable: Runnable) { + val finishRunnable = Runnable { + repository.setStartDisappearAnimation(null) + runnable.run() + } + repository.setStartDisappearAnimation(finishRunnable) + } + + /** Returns whether bouncer is fully showing. */ + fun isFullyShowing(): Boolean { + return (repository.showingSoon.value || repository.isVisible.value) && + repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE && + repository.startingDisappearAnimation.value == null + } + + /** Returns whether bouncer is scrimmed. */ + fun isScrimmed(): Boolean { + return repository.isScrimmed.value + } + + /** If bouncer expansion is between 0f and 1f non-inclusive. */ + fun isInTransit(): Boolean { + return repository.showingSoon.value || + repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN && + repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE + } + + /** Return whether bouncer is animating away. */ + fun isAnimatingAway(): Boolean { + return repository.startingDisappearAnimation.value != null + } + + /** Return whether bouncer will dismiss with actions */ + fun willDismissWithAction(): Boolean { + return repository.onDismissAction.value?.onDismissAction != null + } + + /** Returns whether the bouncer should be full screen. */ + private fun needsFullscreenBouncer(): Boolean { + val mode: KeyguardSecurityModel.SecurityMode = + keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()) + return mode == KeyguardSecurityModel.SecurityMode.SimPin || + mode == KeyguardSecurityModel.SecurityMode.SimPuk + } + + /** Remove the show runnable from the main handler queue to improve performance. */ + private fun cancelShowRunnable() { + DejankUtils.removeCallbacks(showRunnable) + mainHandler.removeCallbacks(showRunnable) + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt index 6e1721490f98..81cf5b41ea71 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt @@ -11,18 +11,16 @@ * 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.compose.gallery +package com.android.systemui.keyguard.shared.model -import androidx.compose.foundation.layout.Column -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.android.systemui.ExampleFeature +import com.android.systemui.plugins.ActivityStarter -/** The screen that shows ExampleFeature. */ -@Composable -fun ExampleFeatureScreen(modifier: Modifier = Modifier) { - Column(modifier) { ExampleFeature("This is an example feature!") } -} +/** Encapsulates callbacks to be invoked by the bouncer logic. */ +// TODO(b/243683121): Move dismiss logic from view controllers +data class BouncerCallbackActionsModel( + val onDismissAction: ActivityStarter.OnDismissAction?, + val cancelAction: Runnable? +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt new file mode 100644 index 000000000000..05cdeaaa106d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.shared.model + +import android.content.res.ColorStateList + +/** Show a keyguard message to the bouncer. */ +data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt new file mode 100644 index 000000000000..ad783da7f304 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.shared.model + +/** Models the state of the lock-screen bouncer */ +data class KeyguardBouncerModel( + val promptReason: Int = 0, + val errorMessage: CharSequence? = null, + val expansionAmount: Float = 0f, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt new file mode 100644 index 000000000000..df260148751c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.ui.binder + +import android.view.KeyEvent +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.internal.policy.SystemBarUtils +import com.android.keyguard.KeyguardHostViewController +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.keyguard.data.BouncerViewDelegate +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch + +/** Binds the bouncer container to its view model. */ +object KeyguardBouncerViewBinder { + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: KeyguardBouncerViewModel, + componentFactory: KeyguardBouncerComponent.Factory + ) { + // Builds the KeyguardHostViewController from bouncer view group. + val hostViewController: KeyguardHostViewController = + componentFactory.create(view).keyguardHostViewController + hostViewController.init() + val delegate = + object : BouncerViewDelegate { + override fun isFullScreenBouncer(): Boolean { + val mode = hostViewController.currentSecurityMode + return mode == KeyguardSecurityModel.SecurityMode.SimPin || + mode == KeyguardSecurityModel.SecurityMode.SimPuk + } + + override fun shouldDismissOnMenuPressed(): Boolean { + return hostViewController.shouldEnableMenuKey() + } + + override fun interceptMediaKey(event: KeyEvent?): Boolean { + return hostViewController.interceptMediaKey(event) + } + + override fun dispatchBackKeyEventPreIme(): Boolean { + return hostViewController.dispatchBackKeyEventPreIme() + } + + override fun showNextSecurityScreenOrFinish(): Boolean { + return hostViewController.dismiss(KeyguardUpdateMonitor.getCurrentUser()) + } + + override fun resume() { + hostViewController.showPrimarySecurityScreen() + hostViewController.onResume() + } + } + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + try { + viewModel.setBouncerViewDelegate(delegate) + launch { + viewModel.show.collect { + hostViewController.showPrimarySecurityScreen() + hostViewController.appear( + SystemBarUtils.getStatusBarHeight(view.context) + ) + } + } + + launch { + viewModel.showPromptReason.collect { prompt -> + hostViewController.showPromptReason(prompt) + } + } + + launch { + viewModel.showBouncerErrorMessage.collect { errorMessage -> + hostViewController.showErrorMessage(errorMessage) + } + } + + launch { + viewModel.showWithFullExpansion.collect { model -> + hostViewController.resetSecurityContainer() + hostViewController.showPromptReason(model.promptReason) + hostViewController.onResume() + } + } + + launch { + viewModel.hide.collect { + hostViewController.cancelDismissAction() + hostViewController.cleanUp() + hostViewController.resetSecurityContainer() + } + } + + launch { + viewModel.startingToHide.collect { hostViewController.onStartingToHide() } + } + + launch { + viewModel.setDismissAction.collect { + hostViewController.setOnDismissAction( + it.onDismissAction, + it.cancelAction + ) + } + } + + launch { + viewModel.startDisappearAnimation.collect { + hostViewController.startDisappearAnimation(it) + } + } + + launch { + viewModel.bouncerExpansionAmount.collect { expansion -> + hostViewController.setExpansion(expansion) + } + } + + launch { + viewModel.bouncerExpansionAmount + .filter { it == EXPANSION_VISIBLE } + .collect { + hostViewController.onResume() + view.announceForAccessibility( + hostViewController.accessibilityTitleForCurrentMode + ) + } + } + + launch { + viewModel.isBouncerVisible.collect { isVisible -> + val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + view.visibility = visibility + hostViewController.onBouncerVisibilityChanged(visibility) + viewModel.notifyBouncerVisibilityHasChanged(visibility) + } + } + + launch { + viewModel.isBouncerVisible + .filter { !it } + .collect { + // Remove existing input for security reasons. + hostViewController.resetSecurityContainer() + } + } + + launch { + viewModel.keyguardPosition.collect { position -> + hostViewController.updateKeyguardPosition(position) + } + } + + launch { + viewModel.updateResources.collect { + hostViewController.updateResources() + viewModel.notifyUpdateResources() + } + } + + launch { + viewModel.bouncerShowMessage.collect { + hostViewController.showMessage(it.message, it.colorStateList) + } + } + + launch { + viewModel.keyguardAuthenticated.collect { + hostViewController.finish(it, KeyguardUpdateMonitor.getCurrentUser()) + viewModel.notifyKeyguardAuthenticated() + } + } + + launch { + viewModel + .observeOnIsBackButtonEnabled { view.systemUiVisibility } + .collect { view.systemUiVisibility = it } + } + + launch { + viewModel.screenTurnedOff.collect { + if (view.visibility == View.VISIBLE) { + hostViewController.onPause() + } + } + } + awaitCancellation() + } finally { + viewModel.setBouncerViewDelegate(null) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt new file mode 100644 index 000000000000..9ad52117bfc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.view.View +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.BouncerViewDelegate +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** Models UI state for the lock screen bouncer; handles user input. */ +class KeyguardBouncerViewModel +@Inject +constructor( + private val view: BouncerView, + private val interactor: BouncerInteractor, +) { + /** Observe on bouncer expansion amount. */ + val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount + + /** Observe on bouncer visibility. */ + val isBouncerVisible: Flow<Boolean> = interactor.isVisible + + /** Observe whether bouncer is showing. */ + val show: Flow<KeyguardBouncerModel> = interactor.show + + /** Observe bouncer prompt when bouncer is showing. */ + val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason } + + /** Observe bouncer error message when bouncer is showing. */ + val showBouncerErrorMessage: Flow<CharSequence> = + interactor.show.map { it.errorMessage }.filterNotNull() + + /** Observe visible expansion when bouncer is showing. */ + val showWithFullExpansion: Flow<KeyguardBouncerModel> = + interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE } + + /** Observe whether bouncer is hiding. */ + val hide: Flow<Unit> = interactor.hide + + /** Observe whether bouncer is starting to hide. */ + val startingToHide: Flow<Unit> = interactor.startingToHide + + /** Observe whether we want to set the dismiss action to the bouncer. */ + val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction + + /** Observe whether we want to start the disappear animation. */ + val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation + + /** Observe whether we want to update keyguard position. */ + val keyguardPosition: Flow<Float> = interactor.keyguardPosition + + /** Observe whether we want to update resources. */ + val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests + + /** Observe whether we want to set a keyguard message when the bouncer shows. */ + val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage + + /** Observe whether keyguard is authenticated already. */ + val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated + + /** Observe whether screen is turned off. */ + val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff + + /** Notify that view visibility has changed. */ + fun notifyBouncerVisibilityHasChanged(visibility: Int) { + return interactor.notifyBouncerVisibilityHasChanged(visibility) + } + /** Observe whether we want to update resources. */ + fun notifyUpdateResources() { + interactor.notifyUpdatedResources() + } + + /** Notify that keyguard authenticated was handled */ + fun notifyKeyguardAuthenticated() { + interactor.notifyKeyguardAuthenticatedHandled() + } + + /** Observe whether back button is enabled. */ + fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> { + return interactor.isBackButtonEnabled.map { enabled -> + var vis: Int = systemUiVisibility() + vis = + if (enabled) { + vis and View.STATUS_BAR_DISABLE_BACK.inv() + } else { + vis or View.STATUS_BAR_DISABLE_BACK + } + vis + } + } + + /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */ + fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) { + view.delegate = delegate + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 5612c22311fb..29e2c1cd8900 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -76,6 +76,14 @@ public class LogModule { return factory.create("NotifInterruptLog", 100); } + /** Provides a logging buffer for notification rendering events. */ + @Provides + @SysUISingleton + @NotificationRenderLog + public static LogBuffer provideNotificationRenderLogBuffer(LogBufferFactory factory) { + return factory.create("NotifRenderLog", 100); + } + /** Provides a logging buffer for all logs for lockscreen to shade transition events. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java new file mode 100644 index 000000000000..8c8753a07339 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for notification rendering logging. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationRenderLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 654c15812988..f1e54e002ef3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.res.ColorStateList @@ -202,6 +203,7 @@ class MediaCarouselController @Inject constructor( * It will be called when the container is out of view. */ lateinit var updateUserVisibility: () -> Unit + lateinit var updateHostVisibility: () -> Unit private val isReorderingAllowed: Boolean get() = visualStabilityProvider.isReorderingAllowed @@ -225,7 +227,13 @@ class MediaCarouselController @Inject constructor( reorderAllPlayers(previousVisiblePlayerKey = null) } - keysNeedRemoval.forEach { removePlayer(it) } + keysNeedRemoval.forEach { + removePlayer(it) + } + if (keysNeedRemoval.size > 0) { + // Carousel visibility may need to be updated after late removals + updateHostVisibility() + } keysNeedRemoval.clear() // Update user visibility so that no extra impression will be logged when @@ -247,6 +255,7 @@ class MediaCarouselController @Inject constructor( receivedSmartspaceCardLatency: Int, isSsReactivated: Boolean ) { + debugLogger.logMediaLoaded(key) if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { @@ -315,7 +324,7 @@ class MediaCarouselController @Inject constructor( data: SmartspaceMediaData, shouldPrioritize: Boolean ) { - if (DEBUG) Log.d(TAG, "Loading Smartspace media update") + debugLogger.logRecommendationLoaded(key) // Log the case where the hidden media carousel with the existed inactive resume // media is shown by the Smartspace signal. if (data.isActive) { @@ -370,13 +379,21 @@ class MediaCarouselController @Inject constructor( } override fun onMediaDataRemoved(key: String) { + debugLogger.logMediaRemoved(key) removePlayer(key) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { - if (DEBUG) Log.d(TAG, "My Smartspace media removal request is received") + debugLogger.logRecommendationRemoved(key, immediately) if (immediately || isReorderingAllowed) { - onMediaDataRemoved(key) + removePlayer(key) + if (!immediately) { + // Although it wasn't requested, we were able to process the removal + // immediately since reordering is allowed. So, notify hosts to update + if (this@MediaCarouselController::updateHostVisibility.isInitialized) { + updateHostVisibility() + } + } } else { keysNeedRemoval.add(key) } @@ -929,6 +946,11 @@ class MediaCarouselController @Inject constructor( mediaManager.onSwipeToDismiss() } + fun getCurrentVisibleMediaContentIntent(): PendingIntent? { + return MediaPlayerData.playerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.apply { println("keysNeedRemoval: $keysNeedRemoval") diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt index 04ebd5a71137..b1018f9544c0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt @@ -40,6 +40,37 @@ class MediaCarouselControllerLogger @Inject constructor( "Removing control panel for $str1 from map without calling #onDestroy" } ) + + fun logMediaLoaded(key: String) = buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "add player $str1" } + ) + + fun logMediaRemoved(key: String) = buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "removing player $str1" } + ) + + fun logRecommendationLoaded(key: String) = buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "add recommendation $str1" } + ) + + fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = immediately + }, + { "removing recommendation $str1, immediate=$bool1" } + ) } private const val TAG = "MediaCarouselCtlrLog" diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index c48271e0348a..896fb4765c57 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -1322,6 +1322,7 @@ class MediaDataManager( println("externalListeners: ${mediaDataFilter.listeners}") println("mediaEntries: $mediaEntries") println("useMediaResumption: $useMediaResumption") + println("allowMediaRecommendations: $allowMediaRecommendations") } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 6baf6e137ab4..e0b6d1f9de7b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -546,6 +546,11 @@ class MediaHierarchyManager @Inject constructor( mediaCarouselController.updateUserVisibility = { mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() } + mediaCarouselController.updateHostVisibility = { + mediaHosts.forEach { + it?.updateViewVisibility() + } + } panelEventsEvents.registerListener(object : NotifPanelEvents.Listener { override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index de2b5c9a4739..864592238b73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -167,7 +167,11 @@ class MediaHost constructor( } } - private fun updateViewVisibility() { + /** + * Updates this host's state based on the current media data's status, and invokes listeners if + * the visibility has changed + */ + fun updateViewVisibility() { state.visible = if (showsOnlyActiveMedia) { mediaDataManager.hasActiveMediaOrRecommendation() } else { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 0f1ee31e066e..c6bd777fbd7a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -24,22 +24,45 @@ import android.os.Bundle import android.os.IBinder import android.os.ResultReceiver import android.os.UserHandle -import android.widget.ImageView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.internal.annotations.VisibleForTesting import com.android.internal.app.ChooserActivity import com.android.internal.app.ResolverListController import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.TargetInfo -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.util.AsyncActivityLauncher import com.android.systemui.R -import com.android.internal.R as AndroidR +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView +import com.android.systemui.mediaprojection.appselector.data.RecentTask +import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter +import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener +import com.android.systemui.util.AsyncActivityLauncher +import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration +import javax.inject.Inject -class MediaProjectionAppSelectorActivity constructor( +class MediaProjectionAppSelectorActivity( private val activityLauncher: AsyncActivityLauncher, + private val controller: MediaProjectionAppSelectorController, + private val recentTasksAdapterFactory: RecentTasksAdapter.Factory, /** This is used to override the dependency in a screenshot test */ @VisibleForTesting - private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null -) : ChooserActivity() { + private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? +) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener { + + @Inject + constructor( + activityLauncher: AsyncActivityLauncher, + controller: MediaProjectionAppSelectorController, + recentTasksAdapterFactory: RecentTasksAdapter.Factory, + ) : this(activityLauncher, controller, recentTasksAdapterFactory, null) + + private var recentsRoot: ViewGroup? = null + private var recentsProgress: View? = null + private var recentsRecycler: RecyclerView? = null override fun getLayoutResource() = R.layout.media_projection_app_selector @@ -52,10 +75,30 @@ class MediaProjectionAppSelectorActivity constructor( // TODO(b/240939253): update copies val title = getString(R.string.media_projection_dialog_service_title) intent.putExtra(Intent.EXTRA_TITLE, title) - super.onCreate(bundle) + controller.init(this) + } - requireViewById<ImageView>(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all) + private fun createRecentsView(parent: ViewGroup): ViewGroup { + val recentsRoot = LayoutInflater.from(this) + .inflate(R.layout.media_projection_recent_tasks, parent, + /* attachToRoot= */ false) as ViewGroup + + recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader) + recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler) + recentsRecycler?.layoutManager = LinearLayoutManager( + this, LinearLayoutManager.HORIZONTAL, + /* reverseLayout= */false + ) + + val itemDecoration = HorizontalSpacerItemDecoration( + resources.getDimensionPixelOffset( + R.dimen.media_projection_app_selector_recents_padding + ) + ) + recentsRecycler?.addItemDecoration(itemDecoration) + + return recentsRoot } override fun appliedThemeResId(): Int = @@ -108,6 +151,7 @@ class MediaProjectionAppSelectorActivity constructor( override fun onDestroy() { activityLauncher.destroy() + controller.destroy() super.onDestroy() } @@ -115,6 +159,27 @@ class MediaProjectionAppSelectorActivity constructor( // do nothing } + override fun bind(recentTasks: List<RecentTask>) { + val recents = recentsRoot ?: return + val progress = recentsProgress ?: return + val recycler = recentsRecycler ?: return + + if (recentTasks.isEmpty()) { + recents.visibility = View.GONE + return + } + + progress.visibility = View.GONE + recycler.visibility = View.VISIBLE + recents.visibility = View.VISIBLE + + recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this) + } + + override fun onRecentClicked(task: RecentTask, view: View) { + // TODO(b/240924732) Handle clicking on a recent task + } + private fun onTargetActivityLaunched(launchToken: IBinder) { if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) { // The client requested to return the result in the result receiver instead of @@ -145,6 +210,14 @@ class MediaProjectionAppSelectorActivity constructor( override fun shouldGetOnlyDefaultActivities() = false + // TODO(b/240924732) flip the flag when the recents selector is ready + override fun shouldShowContentPreview() = false + + override fun createContentPreviewView(parent: ViewGroup): ViewGroup = + recentsRoot ?: createRecentsView(parent).also { + recentsRoot = it + } + companion object { /** * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 8e4ca5a98778..731e348edca4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -380,7 +380,7 @@ class MediaViewController @Inject constructor( type: TYPE ) = traceSection("MediaViewController#attach") { updateMediaViewControllerType(type) - logger.logMediaLocation("attach", currentStartLocation, currentEndLocation) + logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation) this.transitionLayout = transitionLayout layoutController.attach(transitionLayout) if (currentEndLocation == -1) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt index 969699834024..185b4fca87d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt @@ -17,13 +17,29 @@ package com.android.systemui.media.dagger import android.app.Activity +import android.content.ComponentName +import android.content.Context +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.MediaProjectionAppSelectorActivity -import com.android.systemui.util.AsyncActivityLauncher +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController +import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader +import com.android.systemui.mediaprojection.appselector.data.AppIconLoader +import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader +import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader +import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class MediaProjectionAppSelector @Module abstract class MediaProjectionModule { @@ -31,17 +47,43 @@ abstract class MediaProjectionModule { @Binds @IntoMap @ClassKey(MediaProjectionAppSelectorActivity::class) - abstract fun bindMediaProjectionAppSelectorActivity( - activity: MediaProjectionAppSelectorActivity): Activity + abstract fun provideMediaProjectionAppSelectorActivity( + activity: MediaProjectionAppSelectorActivity + ): Activity + + @Binds + abstract fun bindRecentTaskThumbnailLoader( + impl: ActivityTaskManagerThumbnailLoader + ): RecentTaskThumbnailLoader + + @Binds + abstract fun bindRecentTaskListProvider( + impl: ShellRecentTaskListProvider + ): RecentTaskListProvider + + @Binds + abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader companion object { @Provides - fun provideMediaProjectionAppSelectorActivity( - activityLauncher: AsyncActivityLauncher - ): MediaProjectionAppSelectorActivity { - return MediaProjectionAppSelectorActivity( - activityLauncher + fun provideController( + recentTaskListProvider: RecentTaskListProvider, + context: Context, + @MediaProjectionAppSelector scope: CoroutineScope + ): MediaProjectionAppSelectorController { + val appSelectorComponentName = + ComponentName(context, MediaProjectionAppSelectorActivity::class.java) + + return MediaProjectionAppSelectorController( + recentTaskListProvider, + scope, + appSelectorComponentName ) } + + @MediaProjectionAppSelector + @Provides + fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope = + CoroutineScope(applicationScope.coroutineContext + SupervisorJob()) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 2d09ddd7f630..b5166899dafa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -99,6 +99,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private Button mStopButton; private Button mAppButton; private int mListMaxHeight; + private int mItemHeight; private WallpaperColors mWallpaperColors; private Executor mExecutor; private boolean mShouldLaunchLeBroadcastDialog; @@ -106,10 +107,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements MediaOutputBaseAdapter mAdapter; private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> { + ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams(); + int totalItemsHeight = mAdapter.getItemCount() * mItemHeight; + int correctHeight = Math.min(totalItemsHeight, mListMaxHeight); // Set max height for list - if (mDeviceListLayout.getHeight() > mListMaxHeight) { - ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams(); - params.height = mListMaxHeight; + if (correctHeight != params.height) { + params.height = correctHeight; mDeviceListLayout.setLayoutParams(params); } }; @@ -212,6 +215,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mLayoutManager = new LayoutManagerWrapper(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( R.dimen.media_output_dialog_list_max_height); + mItemHeight = context.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_list_item_height); mExecutor = Executors.newSingleThreadExecutor(); } @@ -246,8 +251,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener( mDeviceListLayoutListener); // Init device list + mLayoutManager.setAutoMeasureEnabled(true); mDevicesRecyclerView.setLayoutManager(mLayoutManager); mDevicesRecyclerView.setAdapter(mAdapter); + mDevicesRecyclerView.setHasFixedSize(false); // Init header icon mHeaderIcon.setOnClickListener(v -> onHeaderIconClick()); // Init bottom buttons diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index f040e067bc6d..19b401d80600 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -125,13 +125,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); - private boolean mIsRefreshing = false; - private boolean mNeedRefresh = false; + @VisibleForTesting + boolean mIsRefreshing = false; + @VisibleForTesting + boolean mNeedRefresh = false; private MediaController mMediaController; @VisibleForTesting Callback mCallback; @VisibleForTesting LocalMediaManager mLocalMediaManager; + @VisibleForTesting private MediaOutputMetricLogger mMetricLogger; private int mCurrentState; @@ -995,7 +998,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return; } - if (newState == PlaybackState.STATE_STOPPED || newState == PlaybackState.STATE_PAUSED) { + if (newState == PlaybackState.STATE_STOPPED) { mCallback.onMediaStoppedOrPaused(); } mCurrentState = newState; diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index dc1488eefc4e..53b4d434bfcb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import android.content.Context; import android.util.Log; @@ -77,7 +77,7 @@ public class MediaDreamSentinel extends CoreStartable { public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency, boolean isSsReactivated) { - if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) { + if (!mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt new file mode 100644 index 000000000000..59c6635ed8bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector + +import android.content.ComponentName +import com.android.systemui.media.dagger.MediaProjectionAppSelector +import com.android.systemui.mediaprojection.appselector.data.RecentTask +import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + +class MediaProjectionAppSelectorController( + private val recentTaskListProvider: RecentTaskListProvider, + @MediaProjectionAppSelector private val scope: CoroutineScope, + private val appSelectorComponentName: ComponentName +) { + + fun init(view: MediaProjectionAppSelectorView) { + scope.launch { + val tasks = recentTaskListProvider.loadRecentTasks().sortTasks() + view.bind(tasks) + } + } + + fun destroy() { + scope.cancel() + } + + private fun List<RecentTask>.sortTasks(): List<RecentTask> = + asReversed().sortedBy { + // Show normal tasks first and only then tasks with opened app selector + it.topActivityComponent == appSelectorComponentName + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorView.kt index f9b08000290f..6550aa5569e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorView.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,10 @@ * limitations under the License. */ -@file:JvmName("CommonAssertions") -package com.android.wm.shell.flicker.pip +package com.android.systemui.mediaprojection.appselector -internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity" +import com.android.systemui.mediaprojection.appselector.data.RecentTask + +interface MediaProjectionAppSelectorView { + fun bind(recentTasks: List<RecentTask>) +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt new file mode 100644 index 000000000000..0bdddfe2821d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.data + +import android.content.ComponentName +import android.graphics.drawable.Drawable +import com.android.systemui.dagger.qualifiers.Background +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import javax.inject.Inject + +interface AppIconLoader { + suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? +} + +class IconLoaderLibAppIconLoader +@Inject +constructor( + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : AppIconLoader { + + override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? = + withContext(backgroundDispatcher) { + // TODO(b/240924731): add a blocking call to load an icon using iconloaderlib + null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt new file mode 100644 index 000000000000..6d67e28e15e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.data + +import android.content.ComponentName + +data class RecentTask( + val taskId: Int, + val userId: Int, + val topActivityComponent: ComponentName?, + val baseIntentComponent: ComponentName? +) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt new file mode 100644 index 000000000000..5a0943556d9d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.data + +import com.android.systemui.dagger.qualifiers.Background +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import javax.inject.Inject + +interface RecentTaskListProvider { + suspend fun loadRecentTasks(): List<RecentTask> +} + +class ShellRecentTaskListProvider +@Inject +constructor(@Background private val coroutineDispatcher: CoroutineDispatcher) : + RecentTaskListProvider { + + override suspend fun loadRecentTasks(): List<RecentTask> = + withContext(coroutineDispatcher) { + // TODO(b/240924731): add blocking call to load the recents + emptyList() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt new file mode 100644 index 000000000000..429128032f40 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.data + +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.shared.recents.model.ThumbnailData +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +interface RecentTaskThumbnailLoader { + suspend fun loadThumbnail(taskId: Int): ThumbnailData? +} + +class ActivityTaskManagerThumbnailLoader +@Inject +constructor( + @Background private val coroutineDispatcher: CoroutineDispatcher, +) : + RecentTaskThumbnailLoader { + + override suspend fun loadThumbnail(taskId: Int): ThumbnailData? = + withContext(coroutineDispatcher) { + // TODO(b/240924731): add blocking call to load a thumbnail + null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt new file mode 100644 index 000000000000..ec5abc7a12f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.view + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.media.dagger.MediaProjectionAppSelector +import com.android.systemui.mediaprojection.appselector.data.AppIconLoader +import com.android.systemui.mediaprojection.appselector.data.RecentTask +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +class RecentTaskViewHolder @AssistedInject constructor( + @Assisted root: ViewGroup, + private val iconLoader: AppIconLoader, + private val thumbnailLoader: RecentTaskThumbnailLoader, + @MediaProjectionAppSelector private val scope: CoroutineScope +) : RecyclerView.ViewHolder(root) { + + private val iconView: ImageView = root.requireViewById(R.id.task_icon) + private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail) + + private var job: Job? = null + + fun bind(task: RecentTask, onClick: (View) -> Unit) { + job?.cancel() + + job = + scope.launch { + task.baseIntentComponent?.let { component -> + launch { + val icon = iconLoader.loadIcon(task.userId, component) + iconView.setImageDrawable(icon) + } + } + launch { + val thumbnail = thumbnailLoader.loadThumbnail(task.taskId) + thumbnailView.setImageBitmap(thumbnail?.thumbnail) + } + } + + thumbnailView.setOnClickListener(onClick) + } + + fun onRecycled() { + iconView.setImageDrawable(null) + thumbnailView.setImageBitmap(null) + job?.cancel() + job = null + } + + @AssistedFactory + fun interface Factory { + fun create(root: ViewGroup): RecentTaskViewHolder + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt new file mode 100644 index 000000000000..ec9cfa88f34d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.view + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.mediaprojection.appselector.data.RecentTask +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class RecentTasksAdapter @AssistedInject constructor( + @Assisted private val items: List<RecentTask>, + @Assisted private val listener: RecentTaskClickListener, + private val viewHolderFactory: RecentTaskViewHolder.Factory +) : RecyclerView.Adapter<RecentTaskViewHolder>() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder { + val taskItem = + LayoutInflater.from(parent.context) + .inflate(R.layout.media_projection_task_item, null) as ViewGroup + + return viewHolderFactory.create(taskItem) + } + + override fun onBindViewHolder(holder: RecentTaskViewHolder, position: Int) { + val task = items[position] + holder.bind(task, onClick = { + listener.onRecentClicked(task, holder.itemView) + }) + } + + override fun getItemCount(): Int = items.size + + override fun onViewRecycled(holder: RecentTaskViewHolder) { + holder.onRecycled() + } + + interface RecentTaskClickListener { + fun onRecentClicked(task: RecentTask, view: View) + } + + @AssistedFactory + fun interface Factory { + fun create(items: List<RecentTask>, listener: RecentTaskClickListener): RecentTasksAdapter + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 7c4c64c20089..d605c1a42ec0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -955,9 +955,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker updateDisabledForQuickstep(newConfig); } - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig); - } + // TODO(b/243765256): Disable this logging once b/243765256 is fixed. + Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig + + " lastReportedConfig=" + mLastReportedConfig); mLastReportedConfig.updateFrom(newConfig); updateDisplaySize(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java index cd3609108c28..1d058744ac94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java @@ -28,21 +28,29 @@ import android.widget.ScrollView; public class NonInterceptingScrollView extends ScrollView { private final int mTouchSlop; + private float mDownY; private boolean mScrollEnabled = true; + private boolean mPreventingIntercept; public NonInterceptingScrollView(Context context, AttributeSet attrs) { super(context, attrs); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } + public boolean isPreventingIntercept() { + return mPreventingIntercept; + } + @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: + mPreventingIntercept = false; if (canScrollVertically(1)) { // If we can scroll down, make sure we're not intercepted by the parent + mPreventingIntercept = true; final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); @@ -62,10 +70,13 @@ public class NonInterceptingScrollView extends ScrollView { public boolean onInterceptTouchEvent(MotionEvent ev) { // If there's a touch on this view and we can scroll down, we don't want to be intercepted int action = ev.getActionMasked(); + switch (action) { case MotionEvent.ACTION_DOWN: - // If we can scroll down, make sure non of our parents intercepts us. + mPreventingIntercept = false; + // If we can scroll down, make sure none of our parents intercepts us. if (canScrollVertically(1)) { + mPreventingIntercept = true; final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 7b1ddd62ec6e..ef87fb49752d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -131,6 +131,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { updateClippingPath(); } + public NonInterceptingScrollView getQSPanelContainer() { + return mQSPanelContainer; + } + public void disable(int state1, int state2, boolean animate) { final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; if (disabled == mQsDisabled) return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 7d61991c910a..dea7bb5abd71 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -16,8 +16,13 @@ package com.android.systemui.qs; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED; + import android.content.res.Configuration; +import android.view.MotionEvent; +import android.view.View; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; @@ -30,6 +35,8 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { private final QSPanelController mQsPanelController; private final QuickStatusBarHeaderController mQuickStatusBarHeaderController; private final ConfigurationController mConfigurationController; + private final FalsingManager mFalsingManager; + private final NonInterceptingScrollView mQSPanelContainer; private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -39,14 +46,32 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { } }; + private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + if (mQSPanelContainer.isPreventingIntercept()) { + // There's really no action here to take, but we need to tell the FalsingManager + mFalsingManager.isFalseTouch(QS_SWIPE_NESTED); + } + } + return false; + } + }; + @Inject - QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController, + QSContainerImplController( + QSContainerImpl view, + QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, - ConfigurationController configurationController) { + ConfigurationController configurationController, + FalsingManager falsingManager) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; + mFalsingManager = falsingManager; + mQSPanelContainer = mView.getQSPanelContainer(); } @Override @@ -62,11 +87,13 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { protected void onViewAttached() { mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); mConfigurationController.addCallback(mConfigurationListener); + mQSPanelContainer.setOnTouchListener(mContainerTouchHandler); } @Override protected void onViewDetached() { mConfigurationController.removeCallback(mConfigurationListener); + mQSPanelContainer.setOnTouchListener(null); } public QSContainerImpl getView() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 448e1807f7af..184089f7eef4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -473,6 +473,8 @@ public class QSPanel extends LinearLayout implements Tunable { ? Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0) : 0; layoutParams.topMargin = mediaNeedsTopMargin() && !horizontal ? mMediaTopMargin : 0; + // Call setLayoutParams explicitly to ensure that requestLayout happens + hostView.setLayoutParams(layoutParams); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 59b871cc41bb..f41b905775e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -16,12 +16,11 @@ package com.android.systemui.qs; -import static com.android.systemui.classifier.Classifier.QS_SWIPE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; -import android.content.res.Configuration; import android.view.MotionEvent; import android.view.View; @@ -61,22 +60,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final BrightnessMirrorHandler mBrightnessMirrorHandler; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = - new QSPanel.OnConfigurationChangedListener() { - @Override - public void onConfigurationChange(Configuration newConfig) { - mView.updateResources(); - if (mView.isListening()) { - refreshAllTiles(); - } - } - }; - private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mFalsingManager.isFalseTouch(QS_SWIPE); + mFalsingManager.isFalseTouch(QS_SWIPE_SIDE); } return false; } @@ -130,7 +118,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { if (mView.isListening()) { refreshAllTiles(); } - mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); switchTileLayout(true); mBrightnessMirrorHandler.onQsPanelAttached(); @@ -147,11 +134,18 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override protected void onViewDetached() { mTunerService.removeTunable(mView); - mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener); mBrightnessMirrorHandler.onQsPanelDettached(); super.onViewDetached(); } + @Override + protected void onConfigurationChanged() { + mView.updateResources(); + if (mView.isListening()) { + refreshAllTiles(); + } + } + /** */ public void setVisibility(int visibility) { mView.setVisibility(visibility); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 6e4c858e89df..a5c60a417a05 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -101,11 +101,11 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr + newConfig.windowConfiguration); mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation, mView.getDumpableTag()); - onConfigurationChanged(); if (newConfig.orientation != mLastOrientation) { mLastOrientation = newConfig.orientation; switchTileLayout(false); } + onConfigurationChanged(); } }; @@ -422,6 +422,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } if (mMediaHost != null) { pw.println(" media bounds: " + mMediaHost.getCurrentBounds()); + pw.println(" horizontal layout: " + mUsingHorizontalLayout); + pw.println(" last orientation: " + mLastOrientation); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index c86e6e855c3d..7ce0ad04bb75 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -46,14 +46,6 @@ import javax.inject.Provider; @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = - newConfig -> { - int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles); - if (newMaxTiles != mView.getNumQuickTiles()) { - setMaxTiles(newMaxTiles); - } - }; - private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider; @Inject @@ -99,13 +91,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Override protected void onViewAttached() { super.onViewAttached(); - mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); } @Override protected void onViewDetached() { super.onViewDetached(); - mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener); } private void setMaxTiles(int parseNumTiles) { @@ -115,6 +105,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Override protected void onConfigurationChanged() { + int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles); + if (newMaxTiles != mView.getNumQuickTiles()) { + setMaxTiles(newMaxTiles); + } updateMediaExpansion(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index b6f6e933bf84..624def60276b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -51,8 +51,6 @@ import javax.inject.Inject; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl<BooleanState> { - private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot); - private final HotspotController mHotspotController; private final DataSaverController mDataSaverController; @@ -129,9 +127,6 @@ public class HotspotTile extends QSTileImpl<BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; - if (state.slash == null) { - state.slash = new SlashState(); - } final int numConnectedDevices; final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient(); @@ -150,13 +145,14 @@ public class HotspotTile extends QSTileImpl<BooleanState> { isDataSaverEnabled = mDataSaverController.isDataSaverEnabled(); } - state.icon = mEnabledStatic; state.label = mContext.getString(R.string.quick_settings_hotspot_label); state.isTransient = isTransient; - state.slash.isSlashed = !state.value && !state.isTransient; if (state.isTransient) { state.icon = ResourceIcon.get( - com.android.internal.R.drawable.ic_hotspot_transient_animation); + R.drawable.qs_hotspot_icon_search); + } else { + state.icon = ResourceIcon.get(state.value + ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off); } state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index f60e0661a235..92f6690a13e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -81,8 +81,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; - mUiModeManager = (UiModeManager) host.getUserContext().getSystemService( - Context.UI_MODE_SERVICE); + mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); mLocationController = locationController; configurationController.observe(getLifecycle(), this); batteryController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 0ec4eef1e551..97476b2d1cde 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -16,9 +16,6 @@ package com.android.systemui.qs.tiles; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -42,6 +39,7 @@ import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; @@ -73,7 +71,8 @@ public class UserDetailView extends PseudoGridView { mAdapter.refresh(); } - public static class Adapter extends UserSwitcherController.BaseUserAdapter + /** Provides views for user detail items. */ + public static class Adapter extends BaseUserSwitcherAdapter implements OnClickListener { private final Context mContext; @@ -137,7 +136,7 @@ public class UserDetailView extends PseudoGridView { v.setActivated(item.isCurrent); v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(v); if (item.isCurrent) { mCurrentUserView = v; diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 3788ad98092f..95edb359c53c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -27,6 +27,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -109,6 +110,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -150,6 +152,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final Optional<Pip> mPipOptional; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final Optional<SplitScreen> mSplitScreenOptional; + private final Optional<FloatingTasks> mFloatingTasksOptional; private SysUiState mSysUiState; private final Handler mHandler; private final Lazy<NavigationBarController> mNavBarControllerLazy; @@ -382,7 +385,6 @@ public class OverviewProxyService extends CurrentUserTracker implements mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::togglePanel)); } - private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -466,6 +468,9 @@ public class OverviewProxyService extends CurrentUserTracker implements mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder( KEY_EXTRA_SHELL_SPLIT_SCREEN, splitscreen.createExternalInterface().asBinder())); + mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder( + KEY_EXTRA_SHELL_FLOATING_TASKS, + floatingTasks.createExternalInterface().asBinder())); mOneHandedOptional.ifPresent((onehanded) -> params.putBinder( KEY_EXTRA_SHELL_ONE_HANDED, onehanded.createExternalInterface().asBinder())); @@ -563,6 +568,7 @@ public class OverviewProxyService extends CurrentUserTracker implements NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, + Optional<FloatingTasks> floatingTasksOptional, Optional<OneHanded> oneHandedOptional, Optional<RecentTasks> recentTasks, Optional<BackAnimation> backAnimation, @@ -631,6 +637,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mCommandQueue = commandQueue; mSplitScreenOptional = splitScreenOptional; + mFloatingTasksOptional = floatingTasksOptional; // Listen for user setup startTracking(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 69ee8e8fb8dc..3fee232b3465 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -555,6 +555,8 @@ public class ScreenshotController { mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title))); + mScreenshotView.reset(); + if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { @@ -564,7 +566,6 @@ public class ScreenshotController { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } - mScreenshotView.reset(); } mPackageName = topComponent == null ? "" : topComponent.getPackageName(); mScreenshotView.setPackageName(mPackageName); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 5e7fc6faef1f..360fc879731c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -1006,6 +1006,7 @@ public class ScreenshotView extends FrameLayout implements // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); mScreenshotPreview.setVisibility(View.INVISIBLE); + mScreenshotPreview.setAlpha(1f); mScreenshotPreviewBorder.setAlpha(0); mPendingSharedTransition = false; mActionsContainerBackground.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1011a6d831e6..d7e86b6e2919 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2230,7 +2230,8 @@ public final class NotificationPanelViewController extends PanelViewController { if (cancel) { collapse(false /* delayed */, 1.0f /* speedUpFactor */); } else { - maybeVibrateOnOpening(); + // Window never will receive touch events that typically trigger haptic on open. + maybeVibrateOnOpening(false /* openingWithTouch */); fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */); } onTrackingStopped(false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 8d74a09ab8b1..6be9bbbf4e0d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -31,10 +31,15 @@ import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; +import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -108,7 +113,10 @@ public class NotificationShadeWindowViewController { NotificationShadeWindowController controller, KeyguardUnlockAnimationController keyguardUnlockAnimationController, AmbientState ambientState, - PulsingGestureListener pulsingGestureListener + PulsingGestureListener pulsingGestureListener, + FeatureFlags featureFlags, + KeyguardBouncerViewModel keyguardBouncerViewModel, + KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -130,6 +138,12 @@ public class NotificationShadeWindowViewController { // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); + if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) { + KeyguardBouncerViewBinder.bind( + mView.findViewById(R.id.keyguard_bouncer_container), + keyguardBouncerViewModel, + keyguardBouncerComponentFactory); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java index c3f1e571ab87..b4ce95c434fc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -96,6 +96,7 @@ public abstract class PanelViewController { private float mMinExpandHeight; private boolean mPanelUpdateWhenAnimatorEnds; private final boolean mVibrateOnOpening; + private boolean mHasVibratedOnOpen = false; protected boolean mIsLaunchAnimationRunning; private int mFixedDuration = NO_FIXED_DURATION; protected float mOverExpansion; @@ -353,8 +354,8 @@ public abstract class PanelViewController { private void startOpening(MotionEvent event) { updatePanelExpansionAndVisibility(); - maybeVibrateOnOpening(); - + // Reset at start so haptic can be triggered as soon as panel starts to open. + mHasVibratedOnOpen = false; //TODO: keyguard opens QS a different way; log that too? // Log the position of the swipe that opened the panel @@ -368,9 +369,18 @@ public abstract class PanelViewController { .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); } - protected void maybeVibrateOnOpening() { + /** + * Maybe vibrate as panel is opened. + * + * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead + * being opened programmatically (such as by the open panel gesture), we always play haptic. + */ + protected void maybeVibrateOnOpening(boolean openingWithTouch) { if (mVibrateOnOpening) { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + if (!openingWithTouch || !mHasVibratedOnOpen) { + mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + mHasVibratedOnOpen = true; + } } } @@ -1371,6 +1381,9 @@ public abstract class PanelViewController { break; case MotionEvent.ACTION_MOVE: addMovement(event); + if (!isFullyCollapsed()) { + maybeVibrateOnOpening(true /* openingWithTouch */); + } float h = y - mInitialExpandY; // If the panel was collapsed when touching, we only need to check for the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index e06c97756ea7..073ab8b16864 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1199,7 +1199,8 @@ public class KeyguardIndicationController { return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED - || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED); + || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED + || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED); } private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java deleted file mode 100644 index 4551807499ab..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import android.content.Context; -import android.content.DialogInterface; - -import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.UserSwitcherController; - -public class UserUtil { - public static void deleteUserWithPrompt(Context context, int userId, - UserSwitcherController userSwitcherController) { - new RemoveUserDialog(context, userId, userSwitcherController).show(); - } - - private final static class RemoveUserDialog extends SystemUIDialog implements - DialogInterface.OnClickListener { - - private final int mUserId; - private final UserSwitcherController mUserSwitcherController; - - public RemoveUserDialog(Context context, int userId, - UserSwitcherController userSwitcherController) { - super(context); - setTitle(R.string.user_remove_user_title); - setMessage(context.getString(R.string.user_remove_user_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.user_remove_user_remove), this); - setCanceledOnTouchOutside(false); - mUserId = userId; - mUserSwitcherController = userSwitcherController; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == BUTTON_NEUTRAL) { - cancel(); - } else { - dismiss(); - mUserSwitcherController.removeUserId(mUserId); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt new file mode 100644 index 000000000000..fe03b2ad6a32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.logging + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationRenderLog +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.stack.NotificationSection +import javax.inject.Inject + +/** Handles logging for the {NotificationRoundnessManager}. */ +class NotificationRoundnessLogger +@Inject +constructor(@NotificationRenderLog val buffer: LogBuffer) { + + /** Called when the {NotificationRoundnessManager} updates the corners if the Notifications. */ + fun onCornersUpdated( + view: ExpandableView?, + isFirstInSection: Boolean, + isLastInSection: Boolean, + topChanged: Boolean, + bottomChanged: Boolean + ) { + buffer.log( + TAG_ROUNDNESS, + INFO, + { + str1 = (view as? ExpandableNotificationRow)?.entry?.key + bool1 = isFirstInSection + bool2 = isLastInSection + bool3 = topChanged + bool4 = bottomChanged + }, + { + "onCornersUpdated: " + + "entry=$str1 isFirstInSection=$bool1 isLastInSection=$bool2 " + + "topChanged=$bool3 bottomChanged=$bool4" + } + ) + } + + /** Called when we update the {NotificationRoundnessManager} with new sections. */ + fun onSectionCornersUpdated(sections: Array<NotificationSection?>, anyChanged: Boolean) { + buffer.log( + TAG_ROUNDNESS, + INFO, + { + int1 = sections.size + bool1 = anyChanged + }, + { "onSectionCornersUpdated: sections size=$int1 anyChanged=$bool1" } + ) + } +} + +private const val TAG_ROUNDNESS = "NotifRoundnessLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index b589d9ae1abf..2015c87aac2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -19,12 +19,19 @@ package com.android.systemui.statusbar.notification.stack; import android.content.res.Resources; import android.util.MathUtils; +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import java.io.PrintWriter; import java.util.HashSet; import javax.inject.Inject; @@ -33,12 +40,16 @@ import javax.inject.Inject; * A class that manages the roundness for notification views */ @SysUISingleton -public class NotificationRoundnessManager { +public class NotificationRoundnessManager implements Dumpable { + + private static final String TAG = "NotificationRoundnessManager"; private final ExpandableView[] mFirstInSectionViews; private final ExpandableView[] mLastInSectionViews; private final ExpandableView[] mTmpFirstInSectionViews; private final ExpandableView[] mTmpLastInSectionViews; + private final NotificationRoundnessLogger mNotifLogger; + private final DumpManager mDumpManager; private boolean mExpanded; private HashSet<ExpandableView> mAnimatedChildren; private Runnable mRoundingChangedCallback; @@ -53,12 +64,31 @@ public class NotificationRoundnessManager { @Inject NotificationRoundnessManager( - NotificationSectionsFeatureManager sectionsFeatureManager) { + NotificationSectionsFeatureManager sectionsFeatureManager, + NotificationRoundnessLogger notifLogger, + DumpManager dumpManager) { int numberOfSections = sectionsFeatureManager.getNumberOfBuckets(); mFirstInSectionViews = new ExpandableView[numberOfSections]; mLastInSectionViews = new ExpandableView[numberOfSections]; mTmpFirstInSectionViews = new ExpandableView[numberOfSections]; mTmpLastInSectionViews = new ExpandableView[numberOfSections]; + mNotifLogger = notifLogger; + mDumpManager = dumpManager; + + mDumpManager.registerDumpable(TAG, this); + } + + @Override + public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mFirstInSectionViews: length=" + mFirstInSectionViews.length); + pw.println(dumpViews(mFirstInSectionViews)); + pw.println("mLastInSectionViews: length=" + mLastInSectionViews.length); + pw.println(dumpViews(mFirstInSectionViews)); + if (mTrackedHeadsUp != null) { + pw.println("trackedHeadsUp=" + mTrackedHeadsUp.getEntry()); + } + pw.println("roundForPulsingViews=" + mRoundForPulsingViews); + pw.println("isClearAllInProgress=" + mIsClearAllInProgress); } public void updateView(ExpandableView view, boolean animate) { @@ -95,6 +125,9 @@ public class NotificationRoundnessManager { view.setFirstInSection(isFirstInSection); view.setLastInSection(isLastInSection); + mNotifLogger.onCornersUpdated(view, isFirstInSection, + isLastInSection, topChanged, bottomChanged); + return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged); } @@ -184,6 +217,7 @@ public class NotificationRoundnessManager { if (isLastInSection(view) && !top) { return 1.0f; } + if (view == mTrackedHeadsUp) { // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be // rounded. @@ -220,6 +254,8 @@ public class NotificationRoundnessManager { if (anyChanged) { mRoundingChangedCallback.run(); } + + mNotifLogger.onSectionCornersUpdated(sections, anyChanged); } private boolean handleRemovedOldViews(NotificationSection[] sections, @@ -296,4 +332,36 @@ public class NotificationRoundnessManager { public void setShouldRoundPulsingViews(boolean shouldRoundPulsingViews) { mRoundForPulsingViews = shouldRoundPulsingViews; } + + private String dumpViews(ExpandableView[] views) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < views.length; i++) { + if (views[i] == null) continue; + + sb.append("\t") + .append("[").append(i).append("] ") + .append("isPinned=").append(views[i].isPinned()).append(" ") + .append("isFirstInSection=").append(views[i].isFirstInSection()).append(" ") + .append("isLastInSection=").append(views[i].isLastInSection()).append(" "); + + if (views[i] instanceof ExpandableNotificationRow) { + sb.append("entry="); + dumpEntry(((ExpandableNotificationRow) views[i]).getEntry(), sb); + } + + sb.append("\n"); + } + return sb.toString(); + } + + private void dumpEntry(NotificationEntry entry, StringBuilder sb) { + sb.append("NotificationEntry{key=").append(entry.getKey()).append(" "); + + if (entry.getSection() != null) { + sb.append(" section=") + .append(entry.getSection().getLabel()); + } + + sb.append("}"); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 44aef7de4013..1dface21f540 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -56,7 +56,9 @@ import javax.inject.Inject; /** * A class which manages the bouncer on the lockscreen. + * @deprecated Use KeyguardBouncerRepository */ +@Deprecated public class KeyguardBouncer { private static final String TAG = "KeyguardBouncer"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index 4d6168989691..00c3e8fac0b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.UserSwitcherActivity; import com.android.systemui.util.ViewController; @@ -49,7 +50,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final ActivityStarter mActivityStarter; private final FeatureFlags mFeatureFlags; - private UserSwitcherController.BaseUserAdapter mUserListener; + private BaseUserSwitcherAdapter mUserListener; private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override @@ -135,7 +136,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { final UserSwitcherController controller = mUserSwitcherController; if (controller != null) { - mUserListener = new UserSwitcherController.BaseUserAdapter(controller) { + mUserListener = new BaseUserSwitcherAdapter(controller) { @Override public void notifyDataSetChanged() { mView.refreshContentDescription(getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 6649f3a5bdfd..a8ad564577dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -46,6 +46,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; +import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; @@ -53,6 +54,12 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.data.BouncerView; +import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -123,6 +130,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Nullable private final FoldAodAnimationController mFoldAodAnimationController; private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController; + private final BouncerCallbackInteractor mBouncerCallbackInteractor; + private final BouncerInteractor mBouncerInteractor; + private final BouncerViewDelegate mBouncerViewDelegate; private final Lazy<com.android.systemui.shade.ShadeController> mShadeController; private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() { @@ -197,7 +207,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private View mNotificationContainer; - protected KeyguardBouncer mBouncer; + @Nullable protected KeyguardBouncer mBouncer; protected boolean mShowing; protected boolean mOccluded; protected boolean mRemoteInputActive; @@ -223,6 +233,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private int mLastBiometricMode; private boolean mLastScreenOffAnimationPlaying; private float mQsExpansion; + private boolean mIsModernBouncerEnabled; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -237,6 +248,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final DockManager mDockManager; private final KeyguardUpdateMonitor mKeyguardUpdateManager; private final LatencyTracker mLatencyTracker; + private final KeyguardSecurityModel mKeyguardSecurityModel; private KeyguardBypassController mBypassController; @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor; @@ -271,7 +283,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, Optional<SysUIUnfoldComponent> sysUIUnfoldComponent, Lazy<ShadeController> shadeController, - LatencyTracker latencyTracker) { + LatencyTracker latencyTracker, + KeyguardSecurityModel keyguardSecurityModel, + FeatureFlags featureFlags, + BouncerCallbackInteractor bouncerCallbackInteractor, + BouncerInteractor bouncerInteractor, + BouncerView bouncerView) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -288,8 +305,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; mLatencyTracker = latencyTracker; + mKeyguardSecurityModel = keyguardSecurityModel; + mBouncerCallbackInteractor = bouncerCallbackInteractor; + mBouncerInteractor = bouncerInteractor; + mBouncerViewDelegate = bouncerView.getDelegate(); mFoldAodAnimationController = sysUIUnfoldComponent .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); + mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER); } @Override @@ -303,7 +325,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBiometricUnlockController = biometricUnlockController; ViewGroup container = mCentralSurfaces.getBouncerContainer(); - mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); + if (mIsModernBouncerEnabled) { + mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); + } else { + mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback); + } mNotificationPanelViewController = notificationPanelViewController; if (panelExpansionStateManager != null) { panelExpansionStateManager.addExpansionListener(this); @@ -377,29 +403,45 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mDozing && !mPulsing) { return; } else if (mNotificationPanelViewController.isUnlockHintRunning()) { - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { // Don't expand to the bouncer. Instead transition back to the lock screen (see // CentralSurfaces#showBouncerOrLockScreenIfKeyguard) return; } else if (bouncerNeedsScrimming()) { - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } else if (mShowing && !hideBouncerOverDream) { if (!isWakeAndUnlocking() && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER) && !mCentralSurfaces.isInLaunchTransition() && !isUnlockCollapsing()) { - mBouncer.setExpansion(fraction); + if (mBouncer != null) { + mBouncer.setExpansion(fraction); + } + mBouncerInteractor.setExpansion(fraction); } if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking && !mKeyguardStateController.canDismissLockScreen() - && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) { - mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); + && !bouncerIsShowing() + && !bouncerIsAnimatingAway()) { + if (mBouncer != null) { + mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */); + } + mBouncerInteractor.show(/* isScrimmed= */false); } - } else if (!mShowing && mBouncer.inTransit()) { + } else if (!mShowing && isBouncerInTransit()) { // Keyguard is not visible anymore, but expansion animation was still running. // We need to hide the bouncer, otherwise it will be stuck in transit. - mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + if (mBouncer != null) { + mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + } + mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) { // Panel expanded while pulsing but didn't translate the bouncer (because we are // unlocked.) Let's simply wake-up to dismiss the lock screen. @@ -440,15 +482,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@link KeyguardBouncer#needsFullscreenBouncer()}. */ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { - if (mBouncer.needsFullscreenBouncer() && !mDozing) { + if (needsFullscreenBouncer() && !mDozing) { // The keyguard might be showing (already). So we need to hide it. mCentralSurfaces.hideKeyguard(); - mBouncer.show(true /* resetSecuritySelection */); + if (mBouncer != null) { + mBouncer.show(true /* resetSecuritySelection */); + } + mBouncerInteractor.show(true); } else { mCentralSurfaces.showKeyguard(); if (hideBouncerWhenShowing) { hideBouncer(false /* destroyView */); - mBouncer.prepare(); + if (mBouncer != null) { + mBouncer.prepare(); + } } } updateStates(); @@ -480,10 +527,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ @VisibleForTesting void hideBouncer(boolean destroyView) { - if (mBouncer == null) { - return; + if (mBouncer != null) { + mBouncer.hide(destroyView); } - mBouncer.hide(destroyView); + mBouncerInteractor.hide(); if (mShowing) { // If we were showing the bouncer and then aborting, we need to also clear out any // potential actions unless we actually unlocked. @@ -501,8 +548,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void showBouncer(boolean scrimmed) { resetAlternateAuth(false); - if (mShowing && !mBouncer.isShowing()) { - mBouncer.show(false /* resetSecuritySelection */, scrimmed); + if (mShowing && !isBouncerShowing()) { + if (mBouncer != null) { + mBouncer.show(false /* resetSecuritySelection */, scrimmed); + } + mBouncerInteractor.show(scrimmed); } updateStates(); } @@ -535,7 +585,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // instead of the bouncer. if (shouldShowAltAuth()) { if (!afterKeyguardGone) { - mBouncer.setDismissAction(mAfterKeyguardGoneAction, + if (mBouncer != null) { + mBouncer.setDismissAction(mAfterKeyguardGoneAction, + mKeyguardGoneCancelAction); + } + mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; @@ -549,12 +603,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (afterKeyguardGone) { // we'll handle the dismiss action after keyguard is gone, so just show the // bouncer - mBouncer.show(false /* resetSecuritySelection */); + mBouncerInteractor.show(/* isScrimmed= */true); + if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */); } else { // after authentication success, run dismiss action with the option to defer // hiding the keyguard based on the return value of the OnDismissAction - mBouncer.showWithDismissAction(mAfterKeyguardGoneAction, - mKeyguardGoneCancelAction); + mBouncerInteractor.setDismissAction( + mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); + mBouncerInteractor.show(/* isScrimmed= */true); + if (mBouncer != null) { + mBouncer.showWithDismissAction(mAfterKeyguardGoneAction, + mKeyguardGoneCancelAction); + } // bouncer will handle the dismiss action, so we no longer need to track it here mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; @@ -591,7 +651,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Hide bouncer and quick-quick settings. if (mOccluded && !mDozing) { mCentralSurfaces.hideKeyguard(); - if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) { + if (hideBouncerWhenShowing || needsFullscreenBouncer()) { hideBouncer(false /* destroyView */); } } else { @@ -655,7 +715,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFinishedGoingToSleep() { - mBouncer.onScreenTurnedOff(); + if (mBouncer != null) { + mBouncer.onScreenTurnedOff(); + } + mBouncerInteractor.onScreenTurnedOff(); } @Override @@ -746,7 +809,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // by a FLAG_DISMISS_KEYGUARD_ACTIVITY. reset(isOccluding /* hideBouncerWhenShowing*/); } - if (animate && !mOccluded && mShowing && !mBouncer.isShowing()) { + if (animate && !mOccluded && mShowing && !bouncerIsShowing()) { mCentralSurfaces.animateKeyguardUnoccluding(); } } @@ -762,8 +825,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void startPreHideAnimation(Runnable finishRunnable) { - if (mBouncer.isShowing()) { - mBouncer.startPreHideAnimation(finishRunnable); + if (bouncerIsShowing()) { + if (mBouncer != null) { + mBouncer.startPreHideAnimation(finishRunnable); + } + mBouncerInteractor.startDisappearAnimation(finishRunnable); mCentralSurfaces.onBouncerPreHideAnimation(); // We update the state (which will show the keyguard) only if an animation will run on @@ -873,8 +939,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onThemeChanged() { - boolean wasShowing = mBouncer.isShowing(); - boolean wasScrimmed = mBouncer.isScrimmed(); + if (mIsModernBouncerEnabled) { + updateResources(); + return; + } + boolean wasShowing = bouncerIsShowing(); + boolean wasScrimmed = bouncerIsScrimmed(); hideBouncer(true /* destroyView */); mBouncer.prepare(); @@ -923,7 +993,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * WARNING: This method might cause Binder calls. */ public boolean isSecure() { - return mBouncer.isSecure(); + if (mBouncer != null) { + return mBouncer.isSecure(); + } + + return mKeyguardSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None; } @Override @@ -940,10 +1015,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return whether the back press has been handled */ public boolean onBackPressed(boolean hideImmediately) { - if (mBouncer.isShowing()) { + if (bouncerIsShowing()) { mCentralSurfaces.endAffordanceLaunch(); // The second condition is for SIM card locked bouncer - if (mBouncer.isScrimmed() && !mBouncer.needsFullscreenBouncer()) { + if (bouncerIsScrimmed() + && !needsFullscreenBouncer()) { hideBouncer(false); updateStates(); } else { @@ -956,16 +1032,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean isBouncerShowing() { - return mBouncer.isShowing() || isShowingAlternateAuth(); + return bouncerIsShowing() || isShowingAlternateAuth(); } @Override public boolean bouncerIsOrWillBeShowing() { - return isBouncerShowing() || mBouncer.inTransit(); + return isBouncerShowing() || isBouncerInTransit(); } public boolean isFullscreenBouncer() { - return mBouncer.isFullscreenBouncer(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.isFullScreenBouncer(); + } + return mBouncer != null && mBouncer.isFullscreenBouncer(); } /** @@ -986,7 +1065,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private long getNavBarShowDelay() { if (mKeyguardStateController.isKeyguardFadingAway()) { return mKeyguardStateController.getKeyguardFadingAwayDelay(); - } else if (mBouncer.isShowing()) { + } else if (isBouncerShowing()) { return NAV_BAR_SHOW_DELAY_BOUNCER; } else { // No longer dozing, or remote input is active. No delay. @@ -1009,18 +1088,24 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected void updateStates() { boolean showing = mShowing; boolean occluded = mOccluded; - boolean bouncerShowing = mBouncer.isShowing(); + boolean bouncerShowing = bouncerIsShowing(); boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing(); - boolean bouncerDismissible = !mBouncer.isFullscreenBouncer(); + boolean bouncerDismissible = !isFullscreenBouncer(); boolean remoteInputActive = mRemoteInputActive; if ((bouncerDismissible || !showing || remoteInputActive) != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive) || mFirstUpdate) { if (bouncerDismissible || !showing || remoteInputActive) { - mBouncer.setBackButtonEnabled(true); + if (mBouncer != null) { + mBouncer.setBackButtonEnabled(true); + } + mBouncerInteractor.setBackButtonEnabled(true); } else { - mBouncer.setBackButtonEnabled(false); + if (mBouncer != null) { + mBouncer.setBackButtonEnabled(false); + } + mBouncerInteractor.setBackButtonEnabled(false); } } @@ -1097,7 +1182,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mPulsing && !mIsDocked) && mGesturalNav; return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying - || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav + || bouncerIsShowing() + || mRemoteInputActive + || keyguardWithGestureNav || mGlobalActionsVisible); } @@ -1116,18 +1203,27 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean shouldDismissOnMenuPressed() { - return mBouncer.shouldDismissOnMenuPressed(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.shouldDismissOnMenuPressed(); + } + return mBouncer != null && mBouncer.shouldDismissOnMenuPressed(); } public boolean interceptMediaKey(KeyEvent event) { - return mBouncer.interceptMediaKey(event); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.interceptMediaKey(event); + } + return mBouncer != null && mBouncer.interceptMediaKey(event); } /** * @return true if the pre IME back event should be handled */ public boolean dispatchBackKeyEventPreIme() { - return mBouncer.dispatchBackKeyEventPreIme(); + if (mBouncerViewDelegate != null) { + return mBouncerViewDelegate.dispatchBackKeyEventPreIme(); + } + return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme(); } public void readyForKeyguardDone() { @@ -1150,7 +1246,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean isSecure(int userId) { - return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId); + return isSecure() || mLockPatternUtils.isSecure(userId); } @Override @@ -1173,7 +1269,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * fingerprint. */ public void notifyKeyguardAuthenticated(boolean strongAuth) { - mBouncer.notifyKeyguardAuthenticated(strongAuth); + if (mBouncer != null) { + mBouncer.notifyKeyguardAuthenticated(strongAuth); + } + mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) { resetAlternateAuth(false); @@ -1188,7 +1287,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaController.setMessage(message); } } else { - mBouncer.showMessage(message, colorState); + if (mBouncer != null) { + mBouncer.showMessage(message, colorState); + } + mBouncerInteractor.showMessage(message, colorState); } } @@ -1221,9 +1323,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public boolean bouncerNeedsScrimming() { // When a dream overlay is active, scrimming will cause any expansion to immediately expand. return (mOccluded && !mDreamOverlayStateController.isOverlayActive()) - || mBouncer.willDismissWithAction() - || (mBouncer.isShowing() && mBouncer.isScrimmed()) - || mBouncer.isFullscreenBouncer(); + || bouncerWillDismissWithAction() + || (bouncerIsShowing() + && bouncerIsScrimmed()) + || isFullscreenBouncer(); } /** @@ -1235,6 +1338,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.updateResources(); } + mBouncerInteractor.updateResources(); } public void dump(PrintWriter pw) { @@ -1288,6 +1392,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + @Nullable public KeyguardBouncer getBouncer() { return mBouncer; } @@ -1319,6 +1424,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.updateKeyguardPosition(x); } + + mBouncerInteractor.setKeyguardPosition(x); } private static class DismissWithActionRequest { @@ -1358,9 +1465,65 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Returns if bouncer expansion is between 0 and 1 non-inclusive. */ public boolean isBouncerInTransit() { - if (mBouncer == null) return false; + if (mBouncer != null) { + return mBouncer.inTransit(); + } + + return mBouncerInteractor.isInTransit(); + } + + /** + * Returns if bouncer is showing + */ + public boolean bouncerIsShowing() { + if (mBouncer != null) { + return mBouncer.isShowing(); + } + + return mBouncerInteractor.isFullyShowing(); + } + + /** + * Returns if bouncer is scrimmed + */ + public boolean bouncerIsScrimmed() { + if (mBouncer != null) { + return mBouncer.isScrimmed(); + } + + return mBouncerInteractor.isScrimmed(); + } - return mBouncer.inTransit(); + /** + * Returns if bouncer is animating away + */ + public boolean bouncerIsAnimatingAway() { + if (mBouncer != null) { + return mBouncer.isAnimatingAway(); + } + + return mBouncerInteractor.isAnimatingAway(); + } + + /** + * Returns if bouncer will dismiss with action + */ + public boolean bouncerWillDismissWithAction() { + if (mBouncer != null) { + return mBouncer.willDismissWithAction(); + } + + return mBouncerInteractor.willDismissWithAction(); + } + + /** + * Returns if bouncer needs fullscreen bouncer. i.e. sim pin security method + */ + public boolean needsFullscreenBouncer() { + KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser()); + return mode == KeyguardSecurityModel.SecurityMode.SimPin + || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt new file mode 100644 index 000000000000..5b2d69564585 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.android.systemui.statusbar.policy + +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.widget.BaseAdapter +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserRecordName +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserSwitcherActionIconResourceId +import java.lang.ref.WeakReference + +/** Provides views for user switcher experiences. */ +abstract class BaseUserSwitcherAdapter +protected constructor( + protected val controller: UserSwitcherController, +) : BaseAdapter() { + + protected open val users: ArrayList<UserRecord> + get() = controller.users + + init { + controller.addAdapter(WeakReference(this)) + } + + override fun getCount(): Int { + return if (controller.isKeyguardShowing) { + users.count { !it.isRestricted } + } else { + users.size + } + } + + override fun getItem(position: Int): UserRecord { + return users[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + /** + * Notifies that a user item in the UI has been clicked. + * + * If the user switcher is hosted in a dialog, passing a non-null [dialogShower] will allow + * animation to and from the parent dialog. + */ + @JvmOverloads + fun onUserListItemClicked( + record: UserRecord, + dialogShower: DialogShower? = null, + ) { + controller.onUserListItemClicked(record, dialogShower) + } + + open fun getName(context: Context, item: UserRecord): String { + return getName(context, item, false) + } + + /** Returns the name for the given {@link UserRecord}. */ + open fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { + return getUserRecordName( + context = context, + record = item, + isGuestUserAutoCreated = controller.isGuestUserAutoCreated, + isGuestUserResetting = controller.isGuestUserResetting, + isTablet = isTablet, + ) + } + + fun refresh() { + controller.refreshUsers(UserHandle.USER_NULL) + } + + companion object { + @JvmStatic + protected val disabledUserAvatarColorFilter: ColorFilter by lazy { + val matrix = ColorMatrix() + matrix.setSaturation(0f) // 0 - grayscale + ColorMatrixColorFilter(matrix) + } + + @JvmStatic + @JvmOverloads + protected fun getIconDrawable( + context: Context, + item: UserRecord, + isTablet: Boolean = false, + ): Drawable { + val iconRes = + getUserSwitcherActionIconResourceId( + item.isAddUser, + item.isGuest, + item.isAddSupervisedUser, + isTablet, + ) + return checkNotNull(context.getDrawable(iconRes)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index 753e94015751..149ed0a71b91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -19,14 +19,17 @@ package com.android.systemui.statusbar.policy; import android.annotation.Nullable; import android.view.View; -import com.android.systemui.Dumpable; import com.android.systemui.demomode.DemoMode; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import java.io.PrintWriter; import java.lang.ref.WeakReference; -public interface BatteryController extends DemoMode, Dumpable, +/** + * Controller for battery related information, including the charge level, power save mode, + * and time remaining information + */ +public interface BatteryController extends DemoMode, CallbackController<BatteryStateChangeCallback> { /** * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 33ddf7eed006..c7ad76722929 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -38,11 +38,13 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.PowerUtil; +import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dump.DumpManager; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.util.Assert; @@ -56,7 +58,8 @@ import java.util.concurrent.atomic.AtomicReference; * Default implementation of a {@link BatteryController}. This controller monitors for battery * level change events that are broadcasted by the system. */ -public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController { +public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController, + Dumpable { private static final String TAG = "BatteryController"; private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST"; @@ -70,6 +73,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>(); private final PowerManager mPowerManager; private final DemoModeController mDemoModeController; + private final DumpManager mDumpManager; private final Handler mMainHandler; private final Handler mBgHandler; protected final Context mContext; @@ -101,6 +105,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC PowerManager powerManager, BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, + DumpManager dumpManager, @Main Handler mainHandler, @Background Handler bgHandler) { mContext = context; @@ -110,6 +115,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mEstimates = enhancedEstimates; mBroadcastDispatcher = broadcastDispatcher; mDemoModeController = demoModeController; + mDumpManager = dumpManager; } private void registerReceiver() { @@ -134,6 +140,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } } mDemoModeController.addCallback(this); + mDumpManager.registerDumpable(TAG, this); updatePowerSave(); updateEstimateInBackground(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 163060814545..dc73d1f007c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -69,7 +69,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private final Context mContext; private Resources mResources; private final UserSwitcherController mUserSwitcherController; - private UserSwitcherController.BaseUserAdapter mAdapter; + private BaseUserSwitcherAdapter mAdapter; private final KeyguardStateController mKeyguardStateController; private final FalsingManager mFalsingManager; protected final SysuiStatusBarStateController mStatusBarStateController; @@ -171,7 +171,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); mUserAvatarViewWithBackground = mView.findViewById( R.id.kg_multi_user_avatar_with_background); - mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { + mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e2f5734cb4f9..0995a00533a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -232,14 +229,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } /** - * See: - * - * <ul> - * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li> - * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li> - * </ul> + * Returns {@code true} if the user switcher should be open by default on the lock screen. * - * @return true if the user switcher should be open by default on the lock screen. * @see android.os.UserManager#isUserSwitcherEnabled() */ public boolean isSimpleUserSwitcher() { @@ -436,7 +427,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } static class KeyguardUserAdapter extends - UserSwitcherController.BaseUserAdapter implements View.OnClickListener { + BaseUserSwitcherAdapter implements View.OnClickListener { private final Context mContext; private final Resources mResources; @@ -514,9 +505,9 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS v.bind(name, drawable, item.info.id); } v.setActivated(item.isCurrent); - v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); + v.setDisabledByAdmin(getController().isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + UserSwitcherController.setSelectableAlpha(v); if (item.isCurrent) { mCurrentUserView = v; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt new file mode 100644 index 000000000000..843c2329092c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.android.systemui.statusbar.policy + +import android.annotation.UserIdInt +import android.content.Intent +import android.view.View +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.Dumpable +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper +import java.lang.ref.WeakReference +import kotlinx.coroutines.flow.Flow + +/** Defines interface for a class that provides user switching functionality and state. */ +interface UserSwitcherController : Dumpable { + + /** The current list of [UserRecord]. */ + val users: ArrayList<UserRecord> + + /** Whether the user switcher experience should use the simple experience. */ + val isSimpleUserSwitcher: Boolean + + /** Require a view for jank detection */ + fun init(view: View) + + /** The [UserRecord] of the current user or `null` when none. */ + val currentUserRecord: UserRecord? + + /** The name of the current user of the device or `null`, when none is selected. */ + val currentUserName: String? + + /** + * Notifies that a user has been selected. + * + * This will trigger the right user journeys to create a guest user, switch users, and/or + * navigate to the correct destination. + * + * If a user with the given ID is not found, this method is a no-op. + * + * @param userId The ID of the user to switch to. + * @param dialogShower An optional [DialogShower] in case we need to show dialogs. + */ + fun onUserSelected(userId: Int, dialogShower: DialogShower?) + + /** Whether it is allowed to add users while the device is locked. */ + val isAddUsersFromLockScreenEnabled: Flow<Boolean> + + /** Whether the guest user is configured to always be present on the device. */ + val isGuestUserAutoCreated: Boolean + + /** Whether the guest user is currently being reset. */ + val isGuestUserResetting: Boolean + + /** Creates and switches to the guest user. */ + fun createAndSwitchToGuestUser(dialogShower: DialogShower?) + + /** Shows the add user dialog. */ + fun showAddUserDialog(dialogShower: DialogShower?) + + /** Starts an activity to add a supervised user to the device. */ + fun startSupervisedUserActivity() + + /** Notifies when the display density or font scale has changed. */ + fun onDensityOrFontScaleChanged() + + /** Registers an adapter to notify when the users change. */ + fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) + + /** Notifies the item for a user has been clicked. */ + fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?) + + /** + * Removes guest user and switches to target user. The guest must be the current user and its id + * must be `guestUserId`. + * + * If `targetUserId` is `UserHandle.USER_NULL`, then create a new guest user in the foreground, + * and immediately switch to it. This is used for wiping the current guest and replacing it with + * a new one. + * + * If `targetUserId` is specified, then remove the guest in the background while switching to + * `targetUserId`. + * + * If device is configured with `config_guestUserAutoCreated`, then after guest user is removed, + * a new one is created in the background. This has no effect if `targetUserId` is + * `UserHandle.USER_NULL`. + * + * @param guestUserId id of the guest user to remove + * @param targetUserId id of the user to switch to after guest is removed. If + * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user. + */ + fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) + + /** + * Exits guest user and switches to previous non-guest user. The guest must be the current user. + * + * @param guestUserId user id of the guest user to exit + * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when + * target user id is not known + * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest + * only if its ephemeral, else keep guest + */ + fun exitGuestUser( + @UserIdInt guestUserId: Int, + @UserIdInt targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) + + /** + * Guarantee guest is present only if the device is provisioned. Otherwise, create a content + * observer to wait until the device is provisioned, then schedule the guest creation. + */ + fun schedulePostBootGuestCreation() + + /** Whether keyguard is showing. */ + val isKeyguardShowing: Boolean + + /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */ + fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin? + + /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */ + fun isDisabledByAdmin(record: UserRecord): Boolean + + /** Starts an activity with the given [Intent]. */ + fun startActivity(intent: Intent) + + /** + * Refreshes users from UserManager. + * + * The pictures are only loaded if they have not been loaded yet. + * + * @param forcePictureLoadForId forces the picture of the given user to be reloaded. + */ + fun refreshUsers(forcePictureLoadForId: Int) + + /** Adds a subscriber to when user switches. */ + fun addUserSwitchCallback(callback: UserSwitchCallback) + + /** Removes a previously-added subscriber. */ + fun removeUserSwitchCallback(callback: UserSwitchCallback) + + /** Defines interface for classes that can be called back when the user is switched. */ + fun interface UserSwitchCallback { + /** Notifies that the user has switched. */ + fun onUserSwitched() + } + + companion object { + /** Alpha value to apply to a user view in the user switcher when it's selectable. */ + private const val ENABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA + + /** Alpha value to apply to a user view in the user switcher when it's not selectable. */ + private const val DISABLED_ALPHA = + LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA + + @JvmStatic + fun setSelectableAlpha(view: View) { + view.alpha = + if (view.isEnabled) { + ENABLED_ALPHA + } else { + DISABLED_ALPHA + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt new file mode 100644 index 000000000000..12834f68c3b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.policy + +import android.content.Intent +import android.view.View +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import dagger.Lazy +import java.io.PrintWriter +import java.lang.ref.WeakReference +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Implementation of [UserSwitcherController]. */ +class UserSwitcherControllerImpl +@Inject +constructor( + private val flags: FeatureFlags, + @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>, +) : UserSwitcherController { + + private val isNewImpl: Boolean + get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER) + private val _oldImpl: UserSwitcherControllerOldImpl + get() = oldImpl.get() + + private fun notYetImplemented(): Nothing { + error("Not yet implemented!") + } + + override val users: ArrayList<UserRecord> + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.users + } + + override val isSimpleUserSwitcher: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isSimpleUserSwitcher + } + + override fun init(view: View) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.init(view) + } + } + + override val currentUserRecord: UserRecord? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserRecord + } + + override val currentUserName: String? + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.currentUserName + } + + override fun onUserSelected( + userId: Int, + dialogShower: UserSwitchDialogController.DialogShower? + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserSelected(userId, dialogShower) + } + } + + override val isAddUsersFromLockScreenEnabled: Flow<Boolean> + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isAddUsersFromLockScreenEnabled + } + + override val isGuestUserAutoCreated: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserAutoCreated + } + + override val isGuestUserResetting: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isGuestUserResetting + } + + override fun createAndSwitchToGuestUser( + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.createAndSwitchToGuestUser(dialogShower) + } + } + + override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.showAddUserDialog(dialogShower) + } + } + + override fun startSupervisedUserActivity() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startSupervisedUserActivity() + } + } + + override fun onDensityOrFontScaleChanged() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onDensityOrFontScaleChanged() + } + } + + override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addAdapter(adapter) + } + } + + override fun onUserListItemClicked( + record: UserRecord, + dialogShower: UserSwitchDialogController.DialogShower?, + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.onUserListItemClicked(record, dialogShower) + } + } + + override fun removeGuestUser(guestUserId: Int, targetUserId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeGuestUser(guestUserId, targetUserId) + } + } + + override fun exitGuestUser( + guestUserId: Int, + targetUserId: Int, + forceRemoveGuestOnExit: Boolean + ) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) + } + } + + override fun schedulePostBootGuestCreation() { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.schedulePostBootGuestCreation() + } + } + + override val isKeyguardShowing: Boolean + get() = + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isKeyguardShowing + } + + override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.getEnforcedAdmin(record) + } + } + + override fun isDisabledByAdmin(record: UserRecord): Boolean { + return if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.isDisabledByAdmin(record) + } + } + + override fun startActivity(intent: Intent) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.startActivity(intent) + } + } + + override fun refreshUsers(forcePictureLoadForId: Int) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.refreshUsers(forcePictureLoadForId) + } + } + + override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.addUserSwitchCallback(callback) + } + } + + override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.removeUserSwitchCallback(callback) + } + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + if (isNewImpl) { + notYetImplemented() + } else { + _oldImpl.dump(pw, args) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java index 63e88a6cde67..d365aa6f952d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java @@ -11,9 +11,8 @@ * 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.policy; import static android.os.UserManager.SWITCHABILITY_STATUS_OK; @@ -34,10 +33,6 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -51,7 +46,6 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.View; import android.view.WindowManagerGlobal; -import android.widget.BaseAdapter; import android.widget.Toast; import androidx.annotation.Nullable; @@ -63,7 +57,6 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.users.UserCreatingDialog; -import com.android.systemui.Dumpable; import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.R; @@ -86,7 +79,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import com.android.systemui.user.data.source.UserRecord; -import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -106,15 +98,14 @@ import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlowKt; /** - * Keeps a list of all users on the device for user switching. + * Old implementation. Keeps a list of all users on the device for user switching. + * + * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController} + * instead. */ +@Deprecated @SysUISingleton -public class UserSwitcherController implements Dumpable { - - public static final float USER_SWITCH_ENABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA; - public static final float USER_SWITCH_DISABLED_ALPHA = - LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA; +public class UserSwitcherControllerOldImpl implements UserSwitcherController { private static final String TAG = "UserSwitcherController"; private static final boolean DEBUG = false; @@ -123,7 +114,7 @@ public class UserSwitcherController implements Dumpable { private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l; + private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L; private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"; private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"; @@ -132,7 +123,7 @@ public class UserSwitcherController implements Dumpable { protected final UserTracker mUserTracker; protected final UserManager mUserManager; private final ContentObserver mSettingsObserver; - private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>(); + private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>(); @VisibleForTesting final GuestResumeSessionReceiver mGuestResumeSessionReceiver; @VisibleForTesting @@ -158,7 +149,6 @@ public class UserSwitcherController implements Dumpable { @VisibleForTesting Dialog mAddUserDialog; private int mLastNonGuestUser = UserHandle.USER_SYSTEM; - private boolean mResumeUserOnGuestLogout = true; private boolean mSimpleUserSwitcher; // When false, there won't be any visual affordance to add a new user from the keyguard even if // the user is unlocked @@ -187,7 +177,8 @@ public class UserSwitcherController implements Dumpable { Collections.synchronizedList(new ArrayList<>()); @Inject - public UserSwitcherController(Context context, + public UserSwitcherControllerOldImpl( + Context context, IActivityManager activityManager, UserManager userManager, UserTracker userTracker, @@ -222,9 +213,9 @@ public class UserSwitcherController implements Dumpable { mFalsingManager = falsingManager; mInteractionJankMonitor = interactionJankMonitor; mLatencyTracker = latencyTracker; + mGlobalSettings = globalSettings; mGuestResumeSessionReceiver = guestResumeSessionReceiver; mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver; - mGlobalSettings = globalSettings; mBgExecutor = bgExecutor; mLongRunningExecutor = longRunningExecutor; mUiExecutor = uiExecutor; @@ -303,16 +294,10 @@ public class UserSwitcherController implements Dumpable { refreshUsers(UserHandle.USER_NULL); } - /** - * Refreshes users from UserManager. - * - * The pictures are only loaded if they have not been loaded yet. - * - * @param forcePictureLoadForId forces the picture of the given user to be reloaded. - */ + @Override @SuppressWarnings("unchecked") - private void refreshUsers(int forcePictureLoadForId) { - if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")"); + public void refreshUsers(int forcePictureLoadForId) { + if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")"); if (forcePictureLoadForId != UserHandle.USER_NULL) { mForcePictureLoadForUserId.put(forcePictureLoadForId, true); } @@ -323,8 +308,8 @@ public class UserSwitcherController implements Dumpable { boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL); SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size()); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord r = mUsers.get(i); if (r == null || r.picture == null || r.info == null || forceAllUsers || mForcePictureLoadForUserId.get(r.info.id)) { @@ -431,38 +416,41 @@ public class UserSwitcherController implements Dumpable { }); } - boolean systemCanCreateUsers() { + private boolean systemCanCreateUsers() { return !mUserManager.hasBaseUserRestriction( UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); } - boolean currentUserCanCreateUsers() { + private boolean currentUserCanCreateUsers() { UserInfo currentUser = mUserTracker.getUserInfo(); return currentUser != null && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) && systemCanCreateUsers(); } - boolean anyoneCanCreateUsers() { + private boolean anyoneCanCreateUsers() { return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateGuest(boolean hasExistingGuest) { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && !hasExistingGuest; } + @VisibleForTesting boolean canCreateUser() { return mUserSwitcherEnabled && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); } - boolean createIsRestricted() { + private boolean createIsRestricted() { return !mAddUsersFromLockScreen.getValue(); } + @VisibleForTesting boolean canCreateSupervisedUser() { return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); } @@ -476,7 +464,7 @@ public class UserSwitcherController implements Dumpable { private void notifyAdapters() { for (int i = mAdapters.size() - 1; i >= 0; i--) { - BaseUserAdapter adapter = mAdapters.get(i).get(); + BaseUserSwitcherAdapter adapter = mAdapters.get(i).get(); if (adapter != null) { adapter.notifyDataSetChanged(); } else { @@ -485,37 +473,20 @@ public class UserSwitcherController implements Dumpable { } } + @Override public boolean isSimpleUserSwitcher() { return mSimpleUserSwitcher; } - public void setResumeUserOnGuestLogout(boolean resume) { - mResumeUserOnGuestLogout = resume; - } - /** * Returns whether the current user is a system user. */ - public boolean isSystemUser() { + @VisibleForTesting + boolean isSystemUser() { return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; } - public void removeUserId(int userId) { - if (userId == UserHandle.USER_SYSTEM) { - Log.w(TAG, "User " + userId + " could not removed."); - return; - } - if (mUserTracker.getUserId() == userId) { - switchToUserId(UserHandle.USER_SYSTEM); - } - if (mUserManager.removeUser(userId)) { - refreshUsers(UserHandle.USER_NULL); - } - } - - /** - * @return UserRecord for the current user - */ + @Override public @Nullable UserRecord getCurrentUserRecord() { for (int i = 0; i < mUsers.size(); ++i) { UserRecord userRecord = mUsers.get(i); @@ -526,17 +497,7 @@ public class UserSwitcherController implements Dumpable { return null; } - /** - * Notifies that a user has been selected. - * - * <p>This will trigger the right user journeys to create a guest user, switch users, and/or - * navigate to the correct destination. - * - * <p>If a user with the given ID is not found, this method is a no-op. - * - * @param userId The ID of the user to switch to. - * @param dialogShower An optional {@link DialogShower} in case we need to show dialogs. - */ + @Override public void onUserSelected(int userId, @Nullable DialogShower dialogShower) { UserRecord userRecord = mUsers.stream() .filter(x -> x.resolveId() == userId) @@ -549,23 +510,23 @@ public class UserSwitcherController implements Dumpable { onUserListItemClicked(userRecord, dialogShower); } - /** Whether it is allowed to add users while the device is locked. */ - public Flow<Boolean> getAddUsersFromLockScreen() { + @Override + public Flow<Boolean> isAddUsersFromLockScreenEnabled() { return mAddUsersFromLockScreen; } - /** Returns {@code true} if the guest user is configured to always be present on the device. */ + @Override public boolean isGuestUserAutoCreated() { return mGuestUserAutoCreated; } - /** Returns {@code true} if the guest user is currently being reset. */ + @Override public boolean isGuestUserResetting() { return mGuestIsResetting.get(); } - @VisibleForTesting - void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { + @Override + public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { if (record.isGuest && record.info == null) { createAndSwitchToGuestUser(dialogShower); } else if (record.isAddUser) { @@ -604,7 +565,7 @@ public class UserSwitcherController implements Dumpable { switchToUserId(id); } - protected void switchToUserId(int id) { + private void switchToUserId(int id) { try { if (mView != null) { mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder @@ -621,7 +582,7 @@ public class UserSwitcherController implements Dumpable { private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) { int newId = UserHandle.USER_SYSTEM; - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newId = info.id; @@ -645,9 +606,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Creates and switches to the guest user. - */ + @Override public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) { createGuestAsync(guestId -> { // guestId may be USER_NULL if we haven't reloaded the user list yet. @@ -658,9 +617,7 @@ public class UserSwitcherController implements Dumpable { }); } - /** - * Shows the add user dialog. - */ + @Override public void showAddUserDialog(@Nullable DialogShower dialogShower) { if (mAddUserDialog != null && mAddUserDialog.isShowing()) { mAddUserDialog.cancel(); @@ -677,9 +634,7 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Starts an activity to add a supervised user to the device. - */ + @Override public void startSupervisedUserActivity() { final Intent intent = new Intent() .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) @@ -711,7 +666,7 @@ public class UserSwitcherController implements Dumpable { public void onReceive(Context context, Intent intent) { if (DEBUG) { Log.v(TAG, "Broadcast: a=" + intent.getAction() - + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); + + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); } boolean unpauseRefreshUsers = false; @@ -725,8 +680,8 @@ public class UserSwitcherController implements Dumpable { final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); final UserInfo userInfo = mUserManager.getUserInfo(currentId); - final int N = mUsers.size(); - for (int i = 0; i < N; i++) { + final int userCount = mUsers.size(); + for (int i = 0; i < userCount; i++) { UserRecord record = mUsers.get(i); if (record.info == null) continue; boolean shouldBeCurrent = record.info.id == currentId; @@ -805,7 +760,7 @@ public class UserSwitcherController implements Dumpable { pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated); } - /** Returns the name of the current user of the phone. */ + @Override public String getCurrentUserName() { if (mUsers.isEmpty()) return null; UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null); @@ -814,40 +769,22 @@ public class UserSwitcherController implements Dumpable { return item.info.name; } + @Override public void onDensityOrFontScaleChanged() { refreshUsers(UserHandle.USER_ALL); } - @VisibleForTesting - public void addAdapter(WeakReference<BaseUserAdapter> adapter) { + @Override + public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) { mAdapters.add(adapter); } - @VisibleForTesting + @Override public ArrayList<UserRecord> getUsers() { return mUsers; } - /** - * Removes guest user and switches to target user. The guest must be the current user and its id - * must be {@code guestUserId}. - * - * <p>If {@code targetUserId} is {@link UserHandle#USER_NULL}, then create a new guest user in - * the foreground, and immediately switch to it. This is used for wiping the current guest and - * replacing it with a new one. - * - * <p>If {@code targetUserId} is specified, then remove the guest in the background while - * switching to {@code targetUserId}. - * - * <p>If device is configured with {@link - * com.android.internal.R.bool.config_guestUserAutoCreated}, then after guest user is removed, a - * new one is created in the background. This has no effect if {@code targetUserId} is {@link - * UserHandle#USER_NULL}. - * - * @param guestUserId id of the guest user to remove - * @param targetUserId id of the user to switch to after guest is removed. If {@link - * UserHandle#USER_NULL}, then switch immediately to the newly created guest user. - */ + @Override public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { @@ -894,18 +831,9 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Exits guest user and switches to previous non-guest user. The guest must be the current - * user. - * - * @param guestUserId user id of the guest user to exit - * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when - * target user id is not known - * @param forceRemoveGuestOnExit true: remove guest before switching user, - * false: remove guest only if its ephemeral, else keep guest - */ + @Override public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId, - boolean forceRemoveGuestOnExit) { + boolean forceRemoveGuestOnExit) { UserInfo currentUser = mUserTracker.getUserInfo(); if (currentUser.id != guestUserId) { Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" @@ -921,7 +849,7 @@ public class UserSwitcherController implements Dumpable { int newUserId = UserHandle.USER_SYSTEM; if (targetUserId == UserHandle.USER_NULL) { // when target user is not specified switch to last non guest user - if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { + if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { newUserId = info.id; @@ -959,10 +887,7 @@ public class UserSwitcherController implements Dumpable { } - /** - * Guarantee guest is present only if the device is provisioned. Otherwise, create a content - * observer to wait until the device is provisioned, then schedule the guest creation. - */ + @Override public void schedulePostBootGuestCreation() { if (isDeviceAllowedToAddGuest()) { guaranteeGuestPresent(); @@ -1014,7 +939,7 @@ public class UserSwitcherController implements Dumpable { * @return The multi-user user ID of the newly created guest user, or * {@link UserHandle#USER_NULL} if the guest couldn't be created. */ - public @UserIdInt int createGuest() { + private @UserIdInt int createGuest() { UserInfo guest; try { guest = mUserManager.createGuest(mContext); @@ -1029,135 +954,27 @@ public class UserSwitcherController implements Dumpable { return guest.id; } - /** - * Require a view for jank detection - */ + @Override public void init(View view) { mView = view; } - @VisibleForTesting - public KeyguardStateController getKeyguardStateController() { - return mKeyguardStateController; + @Override + public boolean isKeyguardShowing() { + return mKeyguardStateController.isShowing(); } - /** - * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one. - */ + @Override @Nullable public EnforcedAdmin getEnforcedAdmin(UserRecord record) { return mEnforcedAdminByUserRecord.get(record); } - /** - * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise. - */ + @Override public boolean isDisabledByAdmin(UserRecord record) { return mDisabledByAdmin.contains(record); } - public static abstract class BaseUserAdapter extends BaseAdapter { - - final UserSwitcherController mController; - private final KeyguardStateController mKeyguardStateController; - - protected BaseUserAdapter(UserSwitcherController controller) { - mController = controller; - mKeyguardStateController = controller.getKeyguardStateController(); - controller.addAdapter(new WeakReference<>(this)); - } - - protected ArrayList<UserRecord> getUsers() { - return mController.getUsers(); - } - - public int getUserCount() { - return countUsers(false); - } - - @Override - public int getCount() { - return countUsers(true); - } - - private int countUsers(boolean includeGuest) { - boolean keyguardShowing = mKeyguardStateController.isShowing(); - final int userSize = getUsers().size(); - int count = 0; - for (int i = 0; i < userSize; i++) { - if (getUsers().get(i).isGuest && !includeGuest) { - continue; - } - if (getUsers().get(i).isRestricted && keyguardShowing) { - break; - } - count++; - } - return count; - } - - @Override - public UserRecord getItem(int position) { - return getUsers().get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - /** - * It handles click events on user list items. - * - * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower} - * will allow animation to and from the parent dialog. - * - */ - public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) { - mController.onUserListItemClicked(record, dialogShower); - } - - public void onUserListItemClicked(UserRecord record) { - onUserListItemClicked(record, null); - } - - public String getName(Context context, UserRecord item) { - return getName(context, item, false); - } - - /** - * Returns the name for the given {@link UserRecord}. - */ - public String getName(Context context, UserRecord item, boolean isTablet) { - return LegacyUserUiHelper.getUserRecordName( - context, - item, - mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting(), - isTablet); - } - - protected static ColorFilter getDisabledUserAvatarColorFilter() { - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0f); // 0 - grayscale - return new ColorMatrixColorFilter(matrix); - } - - protected static Drawable getIconDrawable(Context context, UserRecord item) { - return getIconDrawable(context, item, false); - } - protected static Drawable getIconDrawable(Context context, UserRecord item, - boolean isTablet) { - int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); - return context.getDrawable(iconRes); - } - - public void refresh() { - mController.refreshUsers(UserHandle.USER_NULL); - } - } - private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) { EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId()); @@ -1178,20 +995,17 @@ public class UserSwitcherController implements Dumpable { defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0; } + @Override public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } - /** - * Add a subscriber to when user switches. - */ + @Override public void addUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.add(callback); } - /** - * Remove a subscriber to when user switches. - */ + @Override public void removeUserSwitchCallback(UserSwitchCallback callback) { mUserSwitchCallbacks.remove(callback); } @@ -1218,7 +1032,7 @@ public class UserSwitcherController implements Dumpable { // which // helps making the transition faster. if (!mKeyguardStateController.isShowing()) { - mHandler.post(UserSwitcherController.this::notifyAdapters); + mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters); } else { notifyAdapters(); } @@ -1367,13 +1181,4 @@ public class UserSwitcherController implements Dumpable { } } - /** - * Callback to for when this controller receives the intent to switch users. - */ - public interface UserSwitchCallback { - /** - * Called when user has switched. - */ - void onUserSwitched(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 1b7353923ada..b1b45b51d8e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -58,6 +58,8 @@ import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -196,4 +198,8 @@ public interface StatusBarPolicyModule { static DataSaverController provideDataSaverController(NetworkController networkController) { return networkController.getDataSaverController(); } + + /** Binds {@link UserSwitcherController} to its implementation. */ + @Binds + UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 27746c024dad..00ed3d635fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -36,6 +36,7 @@ import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; +import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.gestural.GestureModule; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -116,9 +117,12 @@ public abstract class TvSystemUIModule { static BatteryController provideBatteryController(Context context, EnhancedEstimates enhancedEstimates, PowerManager powerManager, BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, + DumpManager dumpManager, @Main Handler mainHandler, @Background Handler bgHandler) { BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager, - broadcastDispatcher, demoModeController, mainHandler, bgHandler); + broadcastDispatcher, demoModeController, + dumpManager, + mainHandler, bgHandler); bC.init(); return bC; } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 5e2dde6be046..108ab43977e9 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -53,10 +53,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA -import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.ui.binder.UserSwitcherViewBinder import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel @@ -66,10 +64,10 @@ import kotlin.math.ceil private const val USER_VIEW = "user_view" -/** - * Support a fullscreen user switcher - */ -open class UserSwitcherActivity @Inject constructor( +/** Support a fullscreen user switcher */ +open class UserSwitcherActivity +@Inject +constructor( private val userSwitcherController: UserSwitcherController, private val broadcastDispatcher: BroadcastDispatcher, private val falsingCollector: FalsingCollector, @@ -86,11 +84,12 @@ open class UserSwitcherActivity @Inject constructor( private lateinit var addButton: View private var addUserRecords = mutableListOf<UserRecord>() private val onBackCallback = OnBackInvokedCallback { finish() } - private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - finish() + private val userSwitchedCallback: UserTracker.Callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + finish() + } } - } // When the add users options become available, insert another option to manage users private val manageUserRecord = UserRecord( @@ -114,13 +113,14 @@ open class UserSwitcherActivity @Inject constructor( @VisibleForTesting fun createActivity() { setContentView(R.layout.user_switcher_fullscreen) - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + window.decorView.systemUiVisibility = + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) if (isUsingModernArchitecture()) { Log.d(TAG, "Using modern architecture.") - val viewModel = ViewModelProvider( - this, viewModelFactory.get())[UserSwitcherViewModel::class.java] + val viewModel = + ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] UserSwitcherViewBinder.bind( view = requireViewById(R.id.user_switcher_root), viewModel = viewModel, @@ -136,27 +136,23 @@ open class UserSwitcherActivity @Inject constructor( parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root) - parent.touchHandler = object : Gefingerpoken { - override fun onTouchEvent(ev: MotionEvent?): Boolean { - falsingCollector.onTouchEvent(ev) - return false + parent.touchHandler = + object : Gefingerpoken { + override fun onTouchEvent(ev: MotionEvent?): Boolean { + falsingCollector.onTouchEvent(ev) + return false + } } - } - requireViewById<View>(R.id.cancel).apply { - setOnClickListener { - _ -> finish() - } - } + requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } } - addButton = requireViewById<View>(R.id.add).apply { - setOnClickListener { - _ -> showPopupMenu() - } - } + addButton = + requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } } onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback) + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackCallback + ) userSwitcherController.init(parent) initBroadcastReceiver() @@ -169,25 +165,30 @@ open class UserSwitcherActivity @Inject constructor( val items = mutableListOf<UserRecord>() addUserRecords.forEach { items.add(it) } - var popupMenuAdapter = ItemAdapter( - this, - R.layout.user_switcher_fullscreen_popup_item, - layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, - { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { - setTint(resources.getColor( - R.color.user_switcher_fullscreen_popup_item_tint, - getTheme() - )) - } } - ) + var popupMenuAdapter = + ItemAdapter( + this, + R.layout.user_switcher_fullscreen_popup_item, + layoutInflater, + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> + adapter.findUserIcon(item, true).mutate().apply { + setTint( + resources.getColor( + R.color.user_switcher_fullscreen_popup_item_tint, + getTheme() + ) + ) + } + } + ) popupMenuAdapter.addAll(items) - popupMenu = UserSwitcherPopupMenu(this).apply { - setAnchorView(addButton) - setAdapter(popupMenuAdapter) - setOnItemClickListener { - parent: AdapterView<*>, view: View, pos: Int, id: Long -> + popupMenu = + UserSwitcherPopupMenu(this).apply { + setAnchorView(addButton) + setAdapter(popupMenuAdapter) + setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long -> if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) { return@setOnItemClickListener } @@ -206,10 +207,10 @@ open class UserSwitcherActivity @Inject constructor( if (!item.isAddUser) { this@UserSwitcherActivity.finish() } - } + } - show() - } + show() + } } private fun buildUserViews() { @@ -227,8 +228,8 @@ open class UserSwitcherActivity @Inject constructor( val totalWidth = parent.width val userViewCount = adapter.getTotalUserViews() val maxColumns = getMaxColumns(userViewCount) - val horizontalGap = resources - .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) + val horizontalGap = + resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap) val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns @@ -299,14 +300,15 @@ open class UserSwitcherActivity @Inject constructor( } private fun initBroadcastReceiver() { - broadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - finish() + broadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + finish() + } } } - } val filter = IntentFilter() filter.addAction(Intent.ACTION_SCREEN_OFF) @@ -322,9 +324,7 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } - /** - * Provides views to populate the option menu. - */ + /** Provides views to populate the option menu. */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -337,43 +337,27 @@ open class UserSwitcherActivity @Inject constructor( val item = getItem(position) val view = convertView ?: layoutInflater.inflate(resource, parent, false) - view.requireViewById<ImageView>(R.id.icon).apply { - setImageDrawable(iconGetter(item)) - } - view.requireViewById<TextView>(R.id.text).apply { - setText(textGetter(item)) - } + view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) } + view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) } return view } } - private inner class UserAdapter : BaseUserAdapter(userSwitcherController) { + private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val item = getItem(position) var view = convertView as ViewGroup? if (view == null) { - view = layoutInflater.inflate( - R.layout.user_switcher_fullscreen_item, - parent, - false - ) as ViewGroup - } - (view.getChildAt(0) as ImageView).apply { - setImageDrawable(getDrawable(item)) - } - (view.getChildAt(1) as TextView).apply { - setText(getName(getContext(), item)) + view = + layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false) + as ViewGroup } + (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) } + (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) } view.setEnabled(item.isSwitchToEnabled) - view.setAlpha( - if (view.isEnabled()) { - USER_SWITCH_ENABLED_ALPHA - } else { - USER_SWITCH_DISABLED_ALPHA - } - ) + UserSwitcherController.setSelectableAlpha(view) view.setTag(USER_VIEW) return view } @@ -401,23 +385,20 @@ open class UserSwitcherActivity @Inject constructor( } fun getTotalUserViews(): Int { - return users.count { item -> - !doNotRenderUserView(item) - } + return users.count { item -> !doNotRenderUserView(item) } } fun doNotRenderUserView(item: UserRecord): Boolean { - return item.isAddUser || - item.isAddSupervisedUser || - item.isGuest && item.info == null + return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null } private fun getDrawable(item: UserRecord): Drawable { - var drawable = if (item.isGuest) { - getDrawable(R.drawable.ic_account_circle) - } else { - findUserIcon(item) - } + var drawable = + if (item.isGuest) { + getDrawable(R.drawable.ic_account_circle) + } else { + findUserIcon(item) + } drawable.mutate() if (!item.isCurrent && !item.isSwitchToEnabled) { @@ -429,16 +410,16 @@ open class UserSwitcherActivity @Inject constructor( ) } - val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() - as LayerDrawable - if (item == userSwitcherController.getCurrentUserRecord()) { + val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable + if (item == userSwitcherController.currentUserRecord) { (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply { - val stroke = resources - .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) - val color = Utils.getColorAttrDefaultColor( - this@UserSwitcherActivity, - com.android.internal.R.attr.colorAccentPrimary - ) + val stroke = + resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width) + val color = + Utils.getColorAttrDefaultColor( + this@UserSwitcherActivity, + com.android.internal.R.attr.colorAccentPrimary + ) setStroke(stroke, color) } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 305b5ee920a1..035638800f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -99,7 +99,7 @@ constructor( override val actions: Flow<List<UserActionModel>> = userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } } - override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen + override val isActionableWhenLocked: Flow<Boolean> = controller.isAddUsersFromLockScreenEnabled override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index d7ad3cefaf06..938417f9dbe3 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -174,6 +174,7 @@ object UserSwitcherViewBinder { setOnItemClickListener { _, _, position, _ -> val itemPositionExcludingHeader = position - 1 adapter.getItem(itemPositionExcludingHeader).onClicked() + dismiss() } show() diff --git a/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt b/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt new file mode 100644 index 000000000000..ac931e510139 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.util.recycler + +import android.graphics.Rect +import android.view.View +import androidx.annotation.Dimension +import androidx.recyclerview.widget.RecyclerView + +/** + * RecyclerView ItemDecorator that adds a horizontal space of the given size between items + * and double that space on the ends. + */ +class HorizontalSpacerItemDecoration(@Dimension private val offset: Int) : + RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val position: Int = parent.getChildAdapterPosition(view) + val itemCount = parent.adapter?.itemCount ?: 0 + + val left = if (position == 0) offset * 2 else offset + val right = if (position == itemCount - 1) offset * 2 else offset + + outRect.set(left, 0, right, 0) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index 2878ad90835b..0f7e14374e60 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -16,12 +16,21 @@ package com.android.systemui.wallpapers; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER; + import android.app.WallpaperColors; +import android.app.WallpaperManager; +import android.content.ComponentCallbacks2; +import android.content.Context; import android.graphics.Bitmap; +import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; +import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; @@ -31,16 +40,22 @@ import android.util.ArraySet; import android.util.Log; import android.util.MathUtils; import android.util.Size; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface; import android.view.SurfaceHolder; import android.view.WindowManager; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer; import com.android.systemui.wallpapers.gl.EglHelper; import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -59,16 +74,19 @@ public class ImageWallpaper extends WallpaperService { private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS = new RectF(0, 0, 1, 1); private static final boolean DEBUG = false; + private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>(); private final ArraySet<RectF> mColorAreas = new ArraySet<>(); private volatile int mPages = 1; private HandlerThread mWorker; // scaled down version private Bitmap mMiniBitmap; + private final FeatureFlags mFeatureFlags; @Inject - public ImageWallpaper() { + public ImageWallpaper(FeatureFlags featureFlags) { super(); + mFeatureFlags = featureFlags; } @Override @@ -80,7 +98,7 @@ public class ImageWallpaper extends WallpaperService { @Override public Engine onCreateEngine() { - return new GLEngine(); + return mFeatureFlags.isEnabled(USE_CANVAS_RENDERER) ? new CanvasEngine() : new GLEngine(); } @Override @@ -489,4 +507,270 @@ public class ImageWallpaper extends WallpaperService { mRenderer.dump(prefix, fd, out, args); } } + + + class CanvasEngine extends WallpaperService.Engine implements DisplayListener { + + // time [ms] before unloading the wallpaper after it is loaded + private static final int DELAY_FORGET_WALLPAPER = 5000; + + private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper; + + private WallpaperManager mWallpaperManager; + private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer; + private Bitmap mBitmap; + + private Display mDisplay; + private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); + + private AsyncTask<Void, Void, Bitmap> mLoader; + private boolean mNeedsDrawAfterLoadingWallpaper = false; + + CanvasEngine() { + super(); + setFixedSizeAllowed(true); + setShowForAllUsers(true); + } + + void trimMemory(int level) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW + && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL + && isBitmapLoaded()) { + if (DEBUG) { + Log.d(TAG, "trimMemory"); + } + unloadWallpaper(); + } + } + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + if (DEBUG) { + Log.d(TAG, "onCreate"); + } + + mWallpaperManager = getSystemService(WallpaperManager.class); + super.onCreate(surfaceHolder); + + final Context displayContext = getDisplayContext(); + final int displayId = displayContext == null ? DEFAULT_DISPLAY : + displayContext.getDisplayId(); + DisplayManager dm = getSystemService(DisplayManager.class); + if (dm != null) { + mDisplay = dm.getDisplay(displayId); + if (mDisplay == null) { + Log.e(TAG, "Cannot find display! Fallback to default."); + mDisplay = dm.getDisplay(DEFAULT_DISPLAY); + } + } + setOffsetNotificationsEnabled(false); + + mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder); + loadWallpaper(false); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unloadWallpaper(); + } + + @Override + public boolean shouldZoomOutWallpaper() { + return true; + } + + @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (DEBUG) { + Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height); + } + super.onSurfaceChanged(holder, format, width, height); + mImageCanvasWallpaperRenderer.setSurfaceHolder(holder); + drawFrame(false); + } + + @Override + public void onSurfaceDestroyed(SurfaceHolder holder) { + super.onSurfaceDestroyed(holder); + if (DEBUG) { + Log.i(TAG, "onSurfaceDestroyed"); + } + mImageCanvasWallpaperRenderer.setSurfaceHolder(null); + } + + @Override + public void onSurfaceCreated(SurfaceHolder holder) { + super.onSurfaceCreated(holder); + if (DEBUG) { + Log.i(TAG, "onSurfaceCreated"); + } + mImageCanvasWallpaperRenderer.setSurfaceHolder(holder); + } + + @Override + public void onSurfaceRedrawNeeded(SurfaceHolder holder) { + if (DEBUG) { + Log.d(TAG, "onSurfaceRedrawNeeded"); + } + super.onSurfaceRedrawNeeded(holder); + // At the end of this method we should have drawn into the surface. + // This means that the bitmap should be loaded synchronously if + // it was already unloaded. + if (!isBitmapLoaded()) { + setBitmap(mWallpaperManager.getBitmap(true /* hardware */)); + } + drawFrame(true); + } + + private DisplayInfo getDisplayInfo() { + mDisplay.getDisplayInfo(mTmpDisplayInfo); + return mTmpDisplayInfo; + } + + private void drawFrame(boolean forceRedraw) { + if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) { + Log.e(TAG, "attempt to draw a frame without a valid surface"); + return; + } + + if (!isBitmapLoaded()) { + // ensure that we load the wallpaper. + // if the wallpaper is currently loading, this call will have no effect. + loadWallpaper(true); + return; + } + mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw); + } + + private void setBitmap(Bitmap bitmap) { + if (bitmap == null) { + Log.e(TAG, "Attempt to set a null bitmap"); + } else if (mBitmap == bitmap) { + Log.e(TAG, "The value of bitmap is the same"); + } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) { + Log.e(TAG, "Attempt to set an invalid wallpaper of length " + + bitmap.getWidth() + "x" + bitmap.getHeight()); + } else { + if (mBitmap != null) { + mBitmap.recycle(); + } + mBitmap = bitmap; + } + } + + private boolean isBitmapLoaded() { + return mBitmap != null && !mBitmap.isRecycled(); + } + + /** + * Loads the wallpaper on background thread and schedules updating the surface frame, + * and if {@code needsDraw} is set also draws a frame. + * + * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to + * the active request). + * + */ + private void loadWallpaper(boolean needsDraw) { + mNeedsDrawAfterLoadingWallpaper |= needsDraw; + if (mLoader != null) { + if (DEBUG) { + Log.d(TAG, "Skipping loadWallpaper, already in flight "); + } + return; + } + mLoader = new AsyncTask<Void, Void, Bitmap>() { + @Override + protected Bitmap doInBackground(Void... params) { + Throwable exception; + try { + Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */); + if (wallpaper != null + && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { + throw new RuntimeException("Wallpaper is too large to draw!"); + } + return wallpaper; + } catch (RuntimeException | OutOfMemoryError e) { + exception = e; + } + + if (isCancelled()) { + return null; + } + + // Note that if we do fail at this, and the default wallpaper can't + // be loaded, we will go into a cycle. Don't do a build where the + // default wallpaper can't be loaded. + Log.w(TAG, "Unable to load wallpaper!", exception); + try { + mWallpaperManager.clear(); + } catch (IOException ex) { + // now we're really screwed. + Log.w(TAG, "Unable reset to default wallpaper!", ex); + } + + if (isCancelled()) { + return null; + } + + try { + return mWallpaperManager.getBitmap(true /* hardware */); + } catch (RuntimeException | OutOfMemoryError e) { + Log.w(TAG, "Unable to load default wallpaper!", e); + } + return null; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + setBitmap(bitmap); + + if (mNeedsDrawAfterLoadingWallpaper) { + drawFrame(true); + } + + mLoader = null; + mNeedsDrawAfterLoadingWallpaper = false; + scheduleUnloadWallpaper(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void unloadWallpaper() { + if (mLoader != null) { + mLoader.cancel(false); + mLoader = null; + } + + if (mBitmap != null) { + mBitmap.recycle(); + } + mBitmap = null; + + final Surface surface = getSurfaceHolder().getSurface(); + surface.hwuiDestroy(); + mWallpaperManager.forgetLoadedWallpaper(); + } + + private void scheduleUnloadWallpaper() { + Handler handler = getMainThreadHandler(); + handler.removeCallbacks(mUnloadWallpaperCallback); + handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER); + } + + @Override + public void onDisplayAdded(int displayId) { + + } + + @Override + public void onDisplayChanged(int displayId) { + + } + + @Override + public void onDisplayRemoved(int displayId) { + + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java new file mode 100644 index 000000000000..fdba16ed2059 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpapers.canvas; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.util.Log; +import android.view.SurfaceHolder; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Helper to draw a wallpaper on a surface. + * It handles the geometry regarding the dimensions of the display and the wallpaper, + * and rescales the surface and the wallpaper accordingly. + */ +public class ImageCanvasWallpaperRenderer { + + private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName(); + private static final boolean DEBUG = false; + + private SurfaceHolder mSurfaceHolder; + //private Bitmap mBitmap = null; + + @VisibleForTesting + static final int MIN_SURFACE_WIDTH = 128; + @VisibleForTesting + static final int MIN_SURFACE_HEIGHT = 128; + + private boolean mSurfaceRedrawNeeded; + + private int mLastSurfaceWidth = -1; + private int mLastSurfaceHeight = -1; + + public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) { + mSurfaceHolder = surfaceHolder; + } + + /** + * Set the surface holder on which to draw. + * Should be called when the surface holder is created or changed + * @param surfaceHolder the surface on which to draw the wallpaper + */ + public void setSurfaceHolder(SurfaceHolder surfaceHolder) { + mSurfaceHolder = surfaceHolder; + } + + /** + * Check if a surface holder is loaded + * @return true if a valid surfaceHolder has been set. + */ + public boolean isSurfaceHolderLoaded() { + return mSurfaceHolder != null; + } + + /** + * Computes and set the surface dimensions, by using the play and the bitmap dimensions. + * The Bitmap must be loaded before any call to this function + */ + private boolean updateSurfaceSize(Bitmap bitmap) { + int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth()); + int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight()); + boolean surfaceChanged = + surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight; + if (surfaceChanged) { + /* + Used a fixed size surface, because we are special. We can do + this because we know the current design of window animations doesn't + cause this to break. + */ + mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); + mLastSurfaceWidth = surfaceWidth; + mLastSurfaceHeight = surfaceHeight; + } + return surfaceChanged; + } + + /** + * Draw a the wallpaper on the surface. + * The bitmap and the surface must be loaded before calling + * this function. + * @param forceRedraw redraw the wallpaper even if no changes are detected + */ + public void drawFrame(Bitmap bitmap, boolean forceRedraw) { + + if (bitmap == null || bitmap.isRecycled()) { + Log.e(TAG, "Attempt to draw frame before background is loaded:"); + return; + } + + if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) { + Log.e(TAG, "Attempt to set an invalid wallpaper of length " + + bitmap.getWidth() + "x" + bitmap.getHeight()); + return; + } + + mSurfaceRedrawNeeded |= forceRedraw; + boolean surfaceChanged = updateSurfaceSize(bitmap); + + boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded; + mSurfaceRedrawNeeded = false; + + if (!redrawNeeded) { + if (DEBUG) { + Log.d(TAG, "Suppressed drawFrame since redraw is not needed "); + } + return; + } + + if (DEBUG) { + Log.d(TAG, "Redrawing wallpaper"); + } + drawWallpaperWithCanvas(bitmap); + } + + @VisibleForTesting + void drawWallpaperWithCanvas(Bitmap bitmap) { + Canvas c = mSurfaceHolder.lockHardwareCanvas(); + if (c != null) { + Rect dest = mSurfaceHolder.getSurfaceFrame(); + Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: " + + mLastSurfaceWidth + "x" + mLastSurfaceHeight); + try { + c.drawBitmap(bitmap, null, dest, null); + } finally { + mSurfaceHolder.unlockCanvasAndPost(c); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 3961a8bcc980..3472cb1c2a7d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -55,6 +55,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; @@ -106,6 +107,7 @@ public final class WMShell extends CoreStartable private final Optional<Pip> mPipOptional; private final Optional<SplitScreen> mSplitScreenOptional; private final Optional<OneHanded> mOneHandedOptional; + private final Optional<FloatingTasks> mFloatingTasksOptional; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; @@ -166,6 +168,7 @@ public final class WMShell extends CoreStartable Optional<Pip> pipOptional, Optional<SplitScreen> splitScreenOptional, Optional<OneHanded> oneHandedOptional, + Optional<FloatingTasks> floatingTasksOptional, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardStateController keyguardStateController, @@ -190,6 +193,7 @@ public final class WMShell extends CoreStartable mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mUserTracker = userTracker; + mFloatingTasksOptional = floatingTasksOptional; mSysUiMainExecutor = sysUiMainExecutor; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index 2714cf427e19..aca60c033bac 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -86,14 +86,12 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( becauseCannotSkipBouncer = false, biometricSettingEnabledForUser = false, bouncerFullyShown = false, - bouncerIsOrWillShow = false, - onlyFaceEnrolled = false, faceAuthenticated = false, faceDisabled = false, faceLockedOut = false, fpLockedOut = false, goingToSleep = false, - keyguardAwakeExcludingBouncerShowing = false, + keyguardAwake = false, keyguardGoingAway = false, listeningForFaceAssistant = false, occludingAppRequestingFaceAuth = false, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 28e99da49496..43f6f1aac097 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -116,9 +116,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); - when(mUserSwitcherController.getKeyguardStateController()) - .thenReturn(mKeyguardStateController); - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true); mScreenWidth = getUiDevice().getDisplayWidth(); mFakeMeasureSpec = View diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index e3c71282ffc5..12d3d42cb29f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -213,8 +213,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBiometricEnabledCallbackArgCaptor; @Captor private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor; - @Captor - private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor; // Direct executor private final Executor mBackgroundExecutor = Runnable::run; @@ -597,13 +595,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTriesToAuthenticate_whenBouncer() { - fingerprintIsNotEnrolled(); - faceAuthEnabled(); setKeyguardBouncerVisibility(true); verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); - verify(mFaceManager, atLeastOnce()).isHardwareDetected(); - verify(mFaceManager, atLeastOnce()).hasEnrolledTemplates(anyInt()); + verify(mFaceManager).isHardwareDetected(); + verify(mFaceManager).hasEnrolledTemplates(anyInt()); } @Test @@ -1238,9 +1234,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse() throws RemoteException { // Face auth should run when the following is true. - faceAuthEnabled(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); keyguardNotGoingAway(); currentUserIsPrimary(); strongAuthNotRequired(); @@ -1267,7 +1261,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); strongAuthNotRequired(); @@ -1284,7 +1278,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); currentUserIsPrimary(); @@ -1305,11 +1299,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1329,11 +1320,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1352,11 +1341,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1375,7 +1361,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); currentUserIsPrimary(); @@ -1398,8 +1384,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue() throws RemoteException { - // Preconditions for face auth to run - faceAuthEnabled(); + // Face auth should run when the following is true. keyguardNotGoingAway(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); @@ -1411,7 +1396,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); bouncerFullyVisibleAndNotGoingToSleep(); - fingerprintIsNotEnrolled(); mTestableLooper.processAllMessages(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); @@ -1420,7 +1404,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue() throws RemoteException { - // Preconditions for face auth to run + // Face auth should run when the following is true. keyguardNotGoingAway(); currentUserIsPrimary(); currentUserDoesNotHaveTrust(); @@ -1446,7 +1430,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { biometricsNotDisabledThroughDevicePolicyManager(); biometricsEnabledForCurrentUser(); userNotCurrentlySwitching(); - bouncerFullyVisible(); statusBarShadeIsLocked(); mTestableLooper.processAllMessages(); @@ -1460,9 +1443,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardIsVisible(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); statusBarShadeIsNotLocked(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - bouncerNotFullyVisible(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); } @@ -1524,44 +1504,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth() - throws RemoteException { - // Both fingerprint and face are enrolled by default - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsPrimary(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - deviceNotGoingToSleep(); - deviceIsInteractive(); - statusBarShadeIsNotLocked(); - keyguardIsVisible(); - - mTestableLooper.processAllMessages(); - clearInvocations(mUiEventLogger); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - mKeyguardUpdateMonitor.requestFaceAuth(true, - FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - - verify(mFaceManager).authenticate(any(), - mCancellationSignalCaptor.capture(), - mAuthenticationCallbackCaptor.capture(), - any(), - anyInt(), - anyBoolean()); - CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue(); - - bouncerWillBeVisibleSoon(); - mTestableLooper.processAllMessages(); - - assertThat(cancelSignal.isCanceled()).isTrue(); - } - - @Test public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); @@ -1624,21 +1566,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); } - private void faceAuthEnabled() { - // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the - // face manager mock wire-up in setup() - mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId); - } - - private void fingerprintIsNotEnrolled() { - when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false); - // This updates the cached fingerprint state. - // There is no straightforward API to update the fingerprint state. - // It currently works updates after enrollment changes because something else invokes - // startListeningForFingerprint(), which internally calls this method. - mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId); - } - private void statusBarShadeIsNotLocked() { mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); } @@ -1745,19 +1672,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); } - private void bouncerNotFullyVisible() { - setKeyguardBouncerVisibility(false); - } - private void bouncerFullyVisible() { setKeyguardBouncerVisibility(true); } - private void bouncerWillBeVisibleSoon() { - mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false); - mTestableLooper.processAllMessages(); - } - private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index df10dfe9f160..5a26d05d7b37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -1005,18 +1005,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize()); assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize()); - setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) - /* roundedTopDrawable */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) - /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); + doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); mDisplayInfo.rotation = Surface.ROTATION_270; mScreenDecorations.onConfigurationChanged(null); - assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize()); - assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize()); + assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize()); + assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize()); } @Test @@ -1293,51 +1288,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test - public void testOnDisplayChanged_hwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); - decorationSupport.format = PixelFormat.R_8; - doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer; - spyOn(hwcLayer); - doReturn(mDisplay).when(hwcLayer).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(hwcLayer, times(1)).onDisplayChanged(any()); - } - - @Test - public void testOnDisplayChanged_nonHwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) - mScreenDecorations.getOverlayView(R.id.display_cutout); - assertNotNull(cutoutView); - spyOn(cutoutView); - doReturn(mDisplay).when(cutoutView).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(cutoutView, times(1)).onDisplayChanged(any()); - } - - @Test public void testHasSameProvidersWithNullOverlays() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java index 8e00d10a7d39..faa5db4327a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java @@ -17,7 +17,7 @@ package com.android.systemui.classifier; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; -import static com.android.systemui.classifier.Classifier.QS_SWIPE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.google.common.truth.Truth.assertThat; @@ -106,9 +106,9 @@ public class DistanceClassifierTest extends ClassifierTest { } @Test - public void testPass_QsSwipeAlwaysPasses() { + public void testPass_QsSwipeSideAlwaysPasses() { mClassifier.onTouchEvent(appendDownEvent(1, 1)); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 1).isFalse()) + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 1).isFalse()) .isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java index 1d61e29cad35..d70d6fc6baa6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java @@ -22,7 +22,8 @@ import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN; import static com.android.systemui.classifier.Classifier.PULSE_EXPAND; -import static com.android.systemui.classifier.Classifier.QS_SWIPE; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED; +import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import static com.android.systemui.classifier.Classifier.UNLOCK; @@ -323,44 +324,86 @@ public class TypeClassifierTest extends ClassifierTest { } @Test - public void testPass_QsSwipe() { + public void testPass_QsSwipeSide() { when(mDataProvider.isVertical()).thenReturn(false); when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect. when(mDataProvider.isRight()).thenReturn(false); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse(); when(mDataProvider.isUp()).thenReturn(true); when(mDataProvider.isRight()).thenReturn(false); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse(); when(mDataProvider.isUp()).thenReturn(false); when(mDataProvider.isRight()).thenReturn(true); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse(); when(mDataProvider.isUp()).thenReturn(true); when(mDataProvider.isRight()).thenReturn(true); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse(); } @Test - public void testFalse_QsSwipe() { + public void testFalse_QsSwipeSide() { when(mDataProvider.isVertical()).thenReturn(true); when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect. when(mDataProvider.isRight()).thenReturn(false); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue(); when(mDataProvider.isUp()).thenReturn(true); when(mDataProvider.isRight()).thenReturn(false); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue(); when(mDataProvider.isUp()).thenReturn(false); when(mDataProvider.isRight()).thenReturn(true); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue(); when(mDataProvider.isUp()).thenReturn(true); when(mDataProvider.isRight()).thenReturn(true); - assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue(); + assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue(); + } + + @Test + public void testPass_QsNestedSwipe() { + when(mDataProvider.isVertical()).thenReturn(true); + + when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect. + when(mDataProvider.isRight()).thenReturn(false); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse(); + + when(mDataProvider.isUp()).thenReturn(true); + when(mDataProvider.isRight()).thenReturn(false); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse(); + + when(mDataProvider.isUp()).thenReturn(false); + when(mDataProvider.isRight()).thenReturn(true); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse(); + + when(mDataProvider.isUp()).thenReturn(true); + when(mDataProvider.isRight()).thenReturn(true); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse(); + } + + @Test + public void testFalse_QsNestedSwipe() { + when(mDataProvider.isVertical()).thenReturn(false); + + when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect. + when(mDataProvider.isRight()).thenReturn(false); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue(); + + when(mDataProvider.isUp()).thenReturn(true); + when(mDataProvider.isRight()).thenReturn(false); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue(); + + when(mDataProvider.isUp()).thenReturn(false); + when(mDataProvider.isRight()).thenReturn(true); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue(); + + when(mDataProvider.isUp()).thenReturn(true); + when(mDataProvider.isRight()).thenReturn(true); + assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index f93336134900..93a1868b72f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -24,12 +24,11 @@ import androidx.annotation.DrawableRes import androidx.test.filters.SmallTest import com.android.internal.R as InternalR import com.android.systemui.R as SystemUIR -import com.android.systemui.tests.R import com.android.systemui.SysuiTestCase +import com.android.systemui.tests.R import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test - import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -102,14 +101,11 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) - setupResources(radius = 100, - roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px), - roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px)) - + roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f roundedCornerResDelegate.updateDisplayUniqueId(null, 1) - assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize) - assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) + assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index d70467ddeebe..c5a7de410eb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback; @@ -88,6 +89,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Mock ViewRootImpl mViewRoot; + @Mock + BouncerCallbackInteractor mBouncerCallbackInteractor; + DreamOverlayContainerViewController mController; @Before @@ -110,7 +114,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mResources, MAX_BURN_IN_OFFSET, BURN_IN_PROTECTION_UPDATE_INTERVAL, - MILLIS_UNTIL_FULL_JITTER); + MILLIS_UNTIL_FULL_JITTER, + mBouncerCallbackInteractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 9d4275e65302..eec33ca2ff78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -23,8 +23,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; +import android.os.RemoteException; import android.service.dreams.DreamService; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; @@ -57,6 +59,8 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamOverlayServiceTest extends SysuiTestCase { + private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package", + "lowlight"); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -129,7 +133,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase { mDreamOverlayComponentFactory, mStateController, mKeyguardUpdateMonitor, - mUiEventLogger); + mUiEventLogger, + LOW_LIGHT_COMPONENT); } @Test @@ -204,6 +209,22 @@ public class DreamOverlayServiceTest extends SysuiTestCase { } @Test + public void testLowLightSetByIntentExtra() throws RemoteException { + final Intent intent = new Intent(); + intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT); + + final IBinder proxy = mService.onBind(intent); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback); + mMainExecutor.runAllReady(); + + verify(mStateController).setLowLightActive(true); + } + + @Test public void testDestroy() { mService.onDestroy(); mMainExecutor.runAllReady(); @@ -211,6 +232,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mKeyguardUpdateMonitor).removeCallback(any()); verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); verify(mStateController).setOverlayActive(false); + verify(mStateController).setLowLightActive(false); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 2adf2857a385..d1d32a13589a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -218,4 +218,20 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { assertThat(stateController.getComplications(true).contains(complication)) .isTrue(); } + + @Test + public void testNotifyLowLightChanged() { + final DreamOverlayStateController stateController = + new DreamOverlayStateController(mExecutor); + + stateController.addCallback(mCallback); + mExecutor.runAllReady(); + assertThat(stateController.isLowLightActive()).isFalse(); + + stateController.setLowLightActive(true); + + mExecutor.runAllReady(); + verify(mCallback, times(1)).onStateChanged(); + assertThat(stateController.isLowLightActive()).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 4ebae98d1246..aa021781296a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -101,6 +102,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem; @Mock View mStatusBarItemView; + @Mock + DreamOverlayStateController mDreamOverlayStateController; private final Executor mMainExecutor = Runnable::run; @@ -126,7 +129,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { Optional.of(mDreamOverlayNotificationCountProvider), mZenModeController, mStatusBarWindowStateController, - mDreamOverlayStatusBarItemsProvider); + mDreamOverlayStatusBarItemsProvider, + mDreamOverlayStateController); } @Test @@ -137,6 +141,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mZenModeController).addCallback(any()); verify(mDreamOverlayNotificationCountProvider).addCallback(any()); verify(mDreamOverlayStatusBarItemsProvider).addCallback(any()); + verify(mDreamOverlayStateController).addCallback(any()); } @Test @@ -266,7 +271,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { Optional.empty(), mZenModeController, mStatusBarWindowStateController, - mDreamOverlayStatusBarItemsProvider); + mDreamOverlayStatusBarItemsProvider, + mDreamOverlayStateController); controller.onViewAttached(); verify(mView, never()).showIcon( eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); @@ -305,6 +311,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mZenModeController).removeCallback(any()); verify(mDreamOverlayNotificationCountProvider).removeCallback(any()); verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any()); + verify(mDreamOverlayStateController).removeCallback(any()); } @Test @@ -458,6 +465,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Test public void testStatusBarShownWhenSystemStatusBarHidden() { mController.onViewAttached(); + reset(mView); final ArgumentCaptor<StatusBarWindowStateListener> callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); @@ -471,6 +479,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() { mController.onViewAttached(); mController.onViewDetached(); + reset(mView); final ArgumentCaptor<StatusBarWindowStateListener> callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); @@ -493,4 +502,21 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView)); } + + @Test + public void testLowLightHidesStatusBar() { + when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true); + mController.onViewAttached(); + + verify(mView).setVisibility(View.INVISIBLE); + reset(mView); + + when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false); + final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onStateChanged(); + + verify(mView).setVisibility(View.VISIBLE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java index bc944404efe6..522b5b5a8530 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java @@ -16,17 +16,28 @@ package com.android.systemui.dreams.complication; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN; + import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.PendingIntent; +import android.content.Intent; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import androidx.test.filters.SmallTest; +import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.media.MediaCarouselController; import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -48,21 +59,52 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { @Mock private MediaDreamComplication mMediaComplication; + @Mock + private MediaCarouselController mMediaCarouselController; + + @Mock + private ActivityStarter mActivityStarter; + + @Mock + private ActivityIntentHelper mActivityIntentHelper; + + @Mock + private KeyguardStateController mKeyguardStateController; + + @Mock + private NotificationLockscreenUserManager mLockscreenUserManager; + + @Mock + private FeatureFlags mFeatureFlags; + + @Mock + private PendingIntent mPendingIntent; + + private final Intent mIntent = new Intent("android.test.TEST_ACTION"); + private final Integer mCurrentUserId = 99; + @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false); } /** * Ensures clicking media entry chip adds/removes media complication. */ @Test - public void testClick() { + public void testClickToOpenUMO() { final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener.class); @@ -85,10 +127,90 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase { new DreamMediaEntryComplication.DreamMediaEntryViewController( mView, mDreamOverlayStateController, - mMediaComplication); + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); viewController.onViewDetached(); verify(mView).setSelected(false); verify(mDreamOverlayStateController).removeComplication(mMediaComplication); } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaOverLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + true); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).startActivity(mIntent, true, null, true); + } + + /** + * Ensures clicking media entry chip opens media when flag is set. + */ + @Test + public void testClickToOpenMediaDismissingLockscreen() { + when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true); + + when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn( + mPendingIntent); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPendingIntent.getIntent()).thenReturn(mIntent); + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId); + + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication, + mMediaCarouselController, + mActivityStarter, + mActivityIntentHelper, + mKeyguardStateController, + mLockscreenUserManager, + mFeatureFlags); + viewController.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn( + false); + + clickListenerCaptor.getValue().onClick(mView); + verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntent, null); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt new file mode 100644 index 000000000000..3a61c57d086f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.KeyguardBouncer +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class BouncerCallbackInteractorTest : SysuiTestCase() { + private val bouncerCallbackInteractor = BouncerCallbackInteractor() + @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback + @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback) + bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback) + } + + @Test + fun testOnFullyShown() { + bouncerCallbackInteractor.dispatchFullyShown() + verify(bouncerExpansionCallback).onFullyShown() + } + + @Test + fun testOnFullyHidden() { + bouncerCallbackInteractor.dispatchFullyHidden() + verify(bouncerExpansionCallback).onFullyHidden() + } + + @Test + fun testOnExpansionChanged() { + bouncerCallbackInteractor.dispatchExpansionChanged(5f) + verify(bouncerExpansionCallback).onExpansionChanged(5f) + } + + @Test + fun testOnVisibilityChanged() { + bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE) + verify(bouncerExpansionCallback).onVisibilityChanged(false) + } + + @Test + fun testOnStartingToHide() { + bouncerCallbackInteractor.dispatchStartingToHide() + verify(bouncerExpansionCallback).onStartingToHide() + } + + @Test + fun testOnStartingToShow() { + bouncerCallbackInteractor.dispatchStartingToShow() + verify(bouncerExpansionCallback).onStartingToShow() + } + + @Test + fun testOnKeyguardReset() { + bouncerCallbackInteractor.dispatchReset() + verify(keyguardResetCallback).onKeyguardReset() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt new file mode 100644 index 000000000000..e6c8dd87d982 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.DejankUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel +import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN +import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.utils.os.FakeHandler +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class BouncerInteractorTest : SysuiTestCase() { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var repository: KeyguardBouncerRepository + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel + @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + private val mainHandler = FakeHandler(Looper.getMainLooper()) + private lateinit var bouncerInteractor: BouncerInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + DejankUtils.setImmediate(true) + bouncerInteractor = + BouncerInteractor( + repository, + bouncerView, + mainHandler, + keyguardStateController, + keyguardSecurityModel, + bouncerCallbackInteractor, + falsingCollector, + dismissCallbackRegistry, + keyguardBypassController, + keyguardUpdateMonitor, + ) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + `when`(repository.show.value).thenReturn(null) + } + + @Test + fun testShow_isScrimmed() { + bouncerInteractor.show(true) + verify(repository).setShowMessage(null) + verify(repository).setOnScreenTurnedOff(false) + verify(repository).setKeyguardAuthenticated(null) + verify(repository).setHide(false) + verify(repository).setStartingToHide(false) + verify(repository).setScrimmed(true) + verify(repository).setExpansion(EXPANSION_VISIBLE) + verify(repository).setShowingSoon(true) + verify(keyguardStateController).notifyBouncerShowing(true) + verify(bouncerCallbackInteractor).dispatchStartingToShow() + verify(repository).setVisible(true) + verify(repository).setShow(any(KeyguardBouncerModel::class.java)) + verify(repository).setShowingSoon(false) + } + + @Test + fun testShow_isNotScrimmed() { + verify(repository, never()).setExpansion(EXPANSION_VISIBLE) + } + + @Test + fun testShow_keyguardIsDone() { + `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true) + verify(keyguardStateController, never()).notifyBouncerShowing(true) + verify(bouncerCallbackInteractor, never()).dispatchStartingToShow() + } + + @Test + fun testHide() { + bouncerInteractor.hide() + verify(falsingCollector).onBouncerHidden() + verify(keyguardStateController).notifyBouncerShowing(false) + verify(repository).setShowingSoon(false) + verify(repository).setOnDismissAction(null) + verify(repository).setVisible(false) + verify(repository).setHide(true) + verify(repository).setShow(null) + } + + @Test + fun testExpansion() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + bouncerInteractor.setExpansion(0.6f) + verify(repository).setExpansion(0.6f) + verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f) + } + + @Test + fun testExpansion_fullyShown() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + bouncerInteractor.setExpansion(EXPANSION_VISIBLE) + verify(falsingCollector).onBouncerShown() + verify(bouncerCallbackInteractor).dispatchFullyShown() + } + + @Test + fun testExpansion_fullyHidden() { + `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + bouncerInteractor.setExpansion(EXPANSION_HIDDEN) + verify(repository).setVisible(false) + verify(repository).setShow(null) + verify(falsingCollector).onBouncerHidden() + verify(bouncerCallbackInteractor).dispatchReset() + verify(bouncerCallbackInteractor).dispatchFullyHidden() + } + + @Test + fun testExpansion_startingToHide() { + `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) + bouncerInteractor.setExpansion(0.1f) + verify(repository).setStartingToHide(true) + verify(bouncerCallbackInteractor).dispatchStartingToHide() + } + + @Test + fun testShowMessage() { + bouncerInteractor.showMessage("abc", null) + verify(repository).setShowMessage(BouncerShowMessageModel("abc", null)) + } + + @Test + fun testDismissAction() { + val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java) + val cancelAction = mock(Runnable::class.java) + bouncerInteractor.setDismissAction(onDismissAction, cancelAction) + verify(repository) + .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + } + + @Test + fun testUpdateResources() { + bouncerInteractor.updateResources() + verify(repository).setResourceUpdateRequests(true) + } + + @Test + fun testNotifyKeyguardAuthenticated() { + bouncerInteractor.notifyKeyguardAuthenticated(true) + verify(repository).setKeyguardAuthenticated(true) + } + + @Test + fun testOnScreenTurnedOff() { + bouncerInteractor.onScreenTurnedOff() + verify(repository).setOnScreenTurnedOff(true) + } + + @Test + fun testSetKeyguardPosition() { + bouncerInteractor.setKeyguardPosition(0f) + verify(repository).setKeyguardPosition(0f) + } + + @Test + fun testNotifyKeyguardAuthenticatedHandled() { + bouncerInteractor.notifyKeyguardAuthenticatedHandled() + verify(repository).setKeyguardAuthenticated(null) + } + + @Test + fun testNotifyUpdatedResources() { + bouncerInteractor.notifyUpdatedResources() + verify(repository).setResourceUpdateRequests(false) + } + + @Test + fun testSetBackButtonEnabled() { + bouncerInteractor.setBackButtonEnabled(true) + verify(repository).setIsBackButtonEnabled(true) + } + + @Test + fun testStartDisappearAnimation() { + val runnable = mock(Runnable::class.java) + bouncerInteractor.startDisappearAnimation(runnable) + verify(repository).setStartDisappearAnimation(any(Runnable::class.java)) + } + + @Test + fun testIsFullShowing() { + `when`(repository.isVisible.value).thenReturn(true) + `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + assertThat(bouncerInteractor.isFullyShowing()).isTrue() + `when`(repository.isVisible.value).thenReturn(false) + assertThat(bouncerInteractor.isFullyShowing()).isFalse() + } + + @Test + fun testIsScrimmed() { + `when`(repository.isScrimmed.value).thenReturn(true) + assertThat(bouncerInteractor.isScrimmed()).isTrue() + `when`(repository.isScrimmed.value).thenReturn(false) + assertThat(bouncerInteractor.isScrimmed()).isFalse() + } + + @Test + fun testIsInTransit() { + `when`(repository.showingSoon.value).thenReturn(true) + assertThat(bouncerInteractor.isInTransit()).isTrue() + `when`(repository.showingSoon.value).thenReturn(false) + assertThat(bouncerInteractor.isInTransit()).isFalse() + `when`(repository.expansionAmount.value).thenReturn(0.5f) + assertThat(bouncerInteractor.isInTransit()).isTrue() + } + + @Test + fun testIsAnimatingAway() { + `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {}) + assertThat(bouncerInteractor.isAnimatingAway()).isTrue() + `when`(repository.startingDisappearAnimation.value).thenReturn(null) + assertThat(bouncerInteractor.isAnimatingAway()).isFalse() + } + + @Test + fun testWillDismissWithAction() { + `when`(repository.onDismissAction.value?.onDismissAction) + .thenReturn(mock(ActivityStarter.OnDismissAction::class.java)) + assertThat(bouncerInteractor.willDismissWithAction()).isTrue() + `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null) + assertThat(bouncerInteractor.willDismissWithAction()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 5a50a9f64487..e3e3b7413157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.app.PendingIntent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -26,6 +27,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.TransitionLayout @@ -42,6 +44,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @@ -78,6 +81,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var mediaViewController: MediaViewController @Mock lateinit var smartspaceMediaData: SmartspaceMediaData @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> + @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener> private val clock = FakeSystemClock() private lateinit var mediaCarouselController: MediaCarouselController @@ -102,6 +106,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { debugLogger ) verify(mediaDataManager).addListener(capture(listener)) + verify(visualStabilityProvider) + .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer) whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController) whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData) @@ -362,7 +368,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { playerIndex, mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) - assertEquals( playerIndex, 0) + assertEquals(playerIndex, 0) // Replaying the same media player one more time. // And check that the card stays in its position. @@ -374,4 +380,68 @@ class MediaCarouselControllerTest : SysuiTestCase() { playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local") assertEquals(playerIndex, 0) } + + @Test + fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { + var result = false + mediaCarouselController.updateHostVisibility = { result = true } + + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) + listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) + + assertEquals(true, result) + } + + @Test + fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { + var result = false + mediaCarouselController.updateHostVisibility = { result = true } + + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) + listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) + assertEquals(false, result) + + visualStabilityCallback.value.onReorderingAllowed() + assertEquals(true, result) + } + + @Test + fun testGetCurrentVisibleMediaContentIntent() { + val clickIntent1 = mock(PendingIntent::class.java) + val player1 = Triple("player1", + DATA.copy(clickIntent = clickIntent1), + 1000L) + clock.setCurrentTimeMillis(player1.third) + MediaPlayerData.addMediaPlayer(player1.first, + player1.second.copy(notificationKey = player1.first), + panel, clock, isSsReactivated = false) + + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1) + + val clickIntent2 = mock(PendingIntent::class.java) + val player2 = Triple("player2", + DATA.copy(clickIntent = clickIntent2), + 2000L) + clock.setCurrentTimeMillis(player2.third) + MediaPlayerData.addMediaPlayer(player2.first, + player2.second.copy(notificationKey = player2.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the front because it was active more recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + + val clickIntent3 = mock(PendingIntent::class.java) + val player3 = Triple("player3", + DATA.copy(clickIntent = clickIntent3), + 500L) + clock.setCurrentTimeMillis(player3.third) + MediaPlayerData.addMediaPlayer(player3.first, + player3.second.copy(notificationKey = player3.first), + panel, clock, isSsReactivated = false) + + // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is + // added to the end because it was active less recently. + assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 22ecb4b93743..5f643363bd2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -23,6 +23,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.WallpaperColors; +import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.testing.AndroidTestingRunner; import android.view.View; @@ -102,6 +104,18 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void getItemId_validPosition_returnCorrespondingId() { + assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaDevices.get( + 0).getId().hashCode()); + } + + @Test + public void getItemId_invalidPosition_returnPosition() { + int invalidPosition = mMediaDevices.size() + 1; + assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition); + } + + @Test public void onBindViewHolder_bindPairNew_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); @@ -155,6 +169,33 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() { + when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); + } + + @Test + public void onBindViewHolder_isMutingExpectedDevice_verifyView() { + when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true); + when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); + } + + @Test public void onBindViewHolder_initSeekbar_setsVolume() { when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME); when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_CURRENT_VOLUME); @@ -165,6 +206,20 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_bindSelectableDevice_verifyView() { + List<MediaDevice> selectableDevices = new ArrayList<>(); + selectableDevices.add(mMediaDevice2); + when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); + + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + } + + @Test public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); @@ -223,6 +278,22 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_bindGroupingDevice_verifyView() { + when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaDevice1.getState()).thenReturn( + LocalMediaManager.MediaDeviceState.STATE_GROUPING); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); + } + + @Test public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() { when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true); when(mMediaDevice2.getState()).thenReturn( @@ -256,6 +327,31 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() { + when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false); + when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true); + when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false); + when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + mViewHolder.mContainerLayout.performClick(); + + verify(mMediaOutputController).cancelMuteAwaitConnection(); + } + + @Test + public void onItemClick_clicksSelectableDevice_triggerGrouping() { + List<MediaDevice> selectableDevices = new ArrayList<>(); + selectableDevices.add(mMediaDevice2); + when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); + + mViewHolder.mContainerLayout.performClick(); + + verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2); + } + + @Test public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() { when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices); List<MediaDevice> selectableDevices = new ArrayList<>(); @@ -280,4 +376,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue(); } + + @Test + public void updateColorScheme_triggerController() { + WallpaperColors wallpaperColors = WallpaperColors.fromBitmap( + Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)); + + mMediaOutputAdapter.updateColorScheme(wallpaperColors, true); + + verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 6dcf8024eca8..cb31fde26bf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -35,6 +35,7 @@ import android.app.KeyguardManager; import android.app.Notification; import android.content.Context; import android.graphics.drawable.Icon; +import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.MediaDescription; import android.media.MediaMetadata; @@ -279,6 +280,203 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @Test + public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() { + mMediaOutputController.start(mCb); + reset(mCb); + mMediaOutputController.mIsRefreshing = true; + + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + + assertThat(mMediaOutputController.mNeedRefresh).isTrue(); + } + + @Test + public void cancelMuteAwaitConnection_cancelsWithMediaManager() { + when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class)); + mMediaOutputController.start(mCb); + reset(mCb); + + mMediaOutputController.cancelMuteAwaitConnection(); + + verify(mAudioManager).cancelMuteAwaitConnection(any()); + } + + @Test + public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() { + when(mAudioManager.getMutingExpectedDevice()).thenReturn(null); + mMediaOutputController.start(mCb); + reset(mCb); + mMediaOutputController.cancelMuteAwaitConnection(); + + verify(mAudioManager, never()).cancelMuteAwaitConnection(any()); + } + + @Test + public void getAppSourceName_packageNameIsNull_returnsNull() { + MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext, + "", + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, + mKeyguardManager); + testMediaOutputController.start(mCb); + reset(mCb); + + testMediaOutputController.getAppSourceName(); + + assertThat(testMediaOutputController.getAppSourceName()).isNull(); + } + + @Test + public void isActiveItem_deviceNotConnected_returnsFalse() { + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); + + assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse(); + } + + @Test + public void getNotificationSmallIcon_packageNameIsNull_returnsNull() { + MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext, + "", + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, + mKeyguardManager); + testMediaOutputController.start(mCb); + reset(mCb); + + testMediaOutputController.getAppSourceName(); + + assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull(); + } + + @Test + public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() { + mMediaOutputController.start(mCb); + reset(mCb); + mMediaOutputController.mNeedRefresh = true; + + mMediaOutputController.refreshDataSetIfNeeded(); + + assertThat(mMediaOutputController.mNeedRefresh).isFalse(); + } + + @Test + public void isCurrentConnectedDeviceRemote_containsFeatures_returnsTrue() { + when(mMediaDevice1.getFeatures()).thenReturn( + ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)); + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + + assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue(); + } + + @Test + public void addDeviceToPlayMedia_triggersFromLocalMediaManager() { + MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext, + null, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, + mKeyguardManager); + + LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager); + testMediaOutputController.mLocalMediaManager = testLocalMediaManager; + + testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2); + + verify(testLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2); + } + + @Test + public void removeDeviceFromPlayMedia_triggersFromLocalMediaManager() { + MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext, + null, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, + mKeyguardManager); + + LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager); + testMediaOutputController.mLocalMediaManager = testLocalMediaManager; + + testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2); + + verify(testLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2); + } + + @Test + public void getDeselectableMediaDevice_triggersFromLocalMediaManager() { + mMediaOutputController.getDeselectableMediaDevice(); + + verify(mLocalMediaManager).getDeselectableMediaDevice(); + } + + @Test + public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() { + int testVolume = 10; + mMediaOutputController.adjustSessionVolume(testVolume); + + verify(mLocalMediaManager).adjustSessionVolume(testVolume); + } + + @Test + public void getSessionVolumeMax_triggersFromLocalMediaManager() { + mMediaOutputController.getSessionVolumeMax(); + + verify(mLocalMediaManager).getSessionVolumeMax(); + } + + @Test + public void getSessionVolume_triggersFromLocalMediaManager() { + mMediaOutputController.getSessionVolume(); + + verify(mLocalMediaManager).getSessionVolume(); + } + + @Test + public void getSessionName_triggersFromLocalMediaManager() { + mMediaOutputController.getSessionName(); + + verify(mLocalMediaManager).getSessionName(); + } + + @Test + public void releaseSession_triggersFromLocalMediaManager() { + mMediaOutputController.releaseSession(); + + verify(mLocalMediaManager).releaseSession(); + } + + @Test + public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() { + mMediaOutputController.start(mCb); + reset(mCb); + + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + + assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse(); + } + + @Test + public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() { + when(mMediaDevice1.getState()).thenReturn( + LocalMediaManager.MediaDeviceState.STATE_CONNECTING); + mMediaOutputController.start(mCb); + reset(mCb); + + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + + assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue(); + } + + @Test + public void isPlaying_stateIsNull() { + when(mMediaController.getPlaybackState()).thenReturn(null); + + assertThat(mMediaOutputController.isPlaying()).isFalse(); + } + + @Test public void onSelectedDeviceStateChanged_verifyCallback() { when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2); mMediaOutputController.start(mCb); @@ -535,6 +733,44 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @Test + public void getNotificationSmallIcon_withoutSmallIcon_returnsNull() { + final List<NotificationEntry> entryList = new ArrayList<>(); + final NotificationEntry entry = mock(NotificationEntry.class); + final StatusBarNotification sbn = mock(StatusBarNotification.class); + final Notification notification = mock(Notification.class); + entryList.add(entry); + + when(mNotifCollection.getAllNotifs()).thenReturn(entryList); + when(entry.getSbn()).thenReturn(sbn); + when(sbn.getNotification()).thenReturn(notification); + when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(notification.isMediaNotification()).thenReturn(true); + when(notification.getSmallIcon()).thenReturn(null); + + assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull(); + } + + @Test + public void getNotificationSmallIcon_withPackageNameAndMediaSession_returnsIconCompat() { + final List<NotificationEntry> entryList = new ArrayList<>(); + final NotificationEntry entry = mock(NotificationEntry.class); + final StatusBarNotification sbn = mock(StatusBarNotification.class); + final Notification notification = mock(Notification.class); + final Icon icon = mock(Icon.class); + entryList.add(entry); + + when(mNotifCollection.getAllNotifs()).thenReturn(entryList); + when(entry.getSbn()).thenReturn(sbn); + when(sbn.getNotification()).thenReturn(notification); + when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(notification.isMediaNotification()).thenReturn(true); + when(notification.getSmallIcon()).thenReturn(icon); + + assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf( + IconCompat.class); + } + + @Test public void isVolumeControlEnabled_isCastWithVolumeFixed_returnsFalse() { when(mMediaDevice1.getDeviceType()).thenReturn( MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 9557513775f8..bae3569cdc93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -25,7 +25,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.KeyguardManager; +import android.graphics.Bitmap; import android.media.AudioManager; +import android.media.MediaDescription; +import android.media.MediaMetadata; import android.media.MediaRoute2Info; import android.media.session.MediaController; import android.media.session.MediaSessionManager; @@ -43,6 +46,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; @@ -82,6 +86,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); + private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class); + private final MediaDescription mMediaDescription = mock(MediaDescription.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); @@ -100,6 +106,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState); when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE); when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE); + when(mMediaController.getMetadata()).thenReturn(mMediaMetadata); + when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription); mMediaControllers.add(mMediaController); when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers); @@ -207,6 +215,80 @@ public class MediaOutputDialogTest extends SysuiTestCase { } @Test + public void getHeaderIcon_getFromMediaControllerMetaData() { + int testWidth = 10; + int testHeight = 20; + when(mMediaDescription.getIconBitmap()) + .thenReturn(Bitmap.createBitmap(testWidth, testHeight, Bitmap.Config.ARGB_8888)); + + assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getHeight()).isEqualTo( + testHeight); + assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getWidth()).isEqualTo(testWidth); + } + + @Test + public void getHeaderText_getFromMediaControllerMetaData() { + String testTitle = "test title"; + when(mMediaDescription.getTitle()) + .thenReturn(testTitle); + assertThat(mMediaOutputDialog.getHeaderText().toString()).isEqualTo(testTitle); + } + + @Test + public void getHeaderSubtitle_getFromMediaControllerMetaData() { + String testSubtitle = "test title"; + when(mMediaDescription.getSubtitle()) + .thenReturn(testSubtitle); + + assertThat(mMediaOutputDialog.getHeaderSubtitle().toString()).isEqualTo(testSubtitle); + } + + @Test + public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() { + String stopText = mContext.getText(R.string.keyboard_key_media_stop).toString(); + MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); + when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); + + MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, + mockMediaOutputController, mUiEventLogger); + testDialog.show(); + + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + } + + @Test + public void getStopButtonText_supportsBroadcast_returnsBroadcastText() { + String stopText = mContext.getText(R.string.media_output_broadcast).toString(); + MediaDevice mMediaDevice = mock(MediaDevice.class); + MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); + when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true); + when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice); + when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); + when(mockMediaOutputController.isPlaying()).thenReturn(true); + when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); + MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, + mockMediaOutputController, mUiEventLogger); + testDialog.show(); + + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + } + + @Test + public void onStopButtonClick_notPlaying_releaseSession() { + MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); + when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); + when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); + when(mockMediaOutputController.isPlaying()).thenReturn(false); + MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, + mockMediaOutputController, mUiEventLogger); + testDialog.show(); + + testDialog.onStopButtonClick(); + + verify(mockMediaOutputController).releaseSession(); + } + + @Test // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 0bfc0344b521..2f52950a9ee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -16,7 +16,7 @@ package com.android.systemui.media.dream; -import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION; import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; @@ -68,7 +68,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(true); } @Test @@ -137,7 +137,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() { - when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); + when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false); final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt new file mode 100644 index 000000000000..37b7f47fb69e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -0,0 +1,119 @@ +package com.android.systemui.mediaprojection.appselector + +import android.content.ComponentName +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.appselector.data.RecentTask +import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { + + private val taskListProvider = TestRecentTaskListProvider() + private val scope = CoroutineScope(Dispatchers.Unconfined) + private val appSelectorComponentName = ComponentName("com.test", "AppSelector") + + private val view: MediaProjectionAppSelectorView = mock() + + private val controller = MediaProjectionAppSelectorController( + taskListProvider, + scope, + appSelectorComponentName + ) + + @Test + fun initNoRecentTasks_bindsEmptyList() { + taskListProvider.tasks = emptyList() + + controller.init(view) + + verify(view).bind(emptyList()) + } + + @Test + fun initOneRecentTask_bindsList() { + taskListProvider.tasks = listOf( + createRecentTask(taskId = 1) + ) + + controller.init(view) + + verify(view).bind( + listOf( + createRecentTask(taskId = 1) + ) + ) + } + + @Test + fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInReverse() { + val tasks = listOf( + createRecentTask(taskId = 1), + createRecentTask(taskId = 2), + createRecentTask(taskId = 3), + ) + taskListProvider.tasks = tasks + + controller.init(view) + + verify(view).bind( + listOf( + createRecentTask(taskId = 3), + createRecentTask(taskId = 2), + createRecentTask(taskId = 1), + ) + ) + } + + @Test + fun initRecentTasksWithAppSelectorTasks_bindsListInReverseAndAppSelectorTasksAtTheEnd() { + val tasks = listOf( + createRecentTask(taskId = 1), + createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName), + createRecentTask(taskId = 3), + createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName), + createRecentTask(taskId = 5), + ) + taskListProvider.tasks = tasks + + controller.init(view) + + verify(view).bind( + listOf( + createRecentTask(taskId = 5), + createRecentTask(taskId = 3), + createRecentTask(taskId = 1), + createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName), + createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName), + ) + ) + } + + private fun createRecentTask( + taskId: Int, + topActivityComponent: ComponentName? = null + ): RecentTask { + return RecentTask( + taskId = taskId, + topActivityComponent = topActivityComponent, + baseIntentComponent = ComponentName("com", "Test"), + userId = 0 + ) + } + + private class TestRecentTaskListProvider : RecentTaskListProvider { + + var tasks: List<RecentTask> = emptyList() + + override suspend fun loadRecentTasks(): List<RecentTask> = tasks + + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index ecc845720717..cbe118635e95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -226,7 +226,9 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { + " Tile records:\n" + " " + mockTileString + "\n" + " " + mockTileViewString + "\n" - + " media bounds: null\n"; + + " media bounds: null\n" + + " horizontal layout: false\n" + + " last orientation: 0\n"; assertEquals(expected, w.getBuffer().toString()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 39f27d43ed12..4af5b9018d5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -123,8 +123,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Test fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() { - // times(2) because both controller and base controller are registering their listeners - verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) + verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture()) // verify that media starts in the expanded state by default verify(mediaHost).expansion = MediaHostState.EXPANDED @@ -139,8 +138,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Test fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() { - // times(2) because both controller and base controller are registering their listeners - verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) + verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture()) reset(mediaHost) usingCollapsedLandscapeMedia = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index b86713d0890b..451e9119f297 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.HotspotController; @@ -122,4 +123,40 @@ public class HotspotTileTest extends SysuiTestCase { .isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network)); mockitoSession.finishMocking(); } + + @Test + public void testIcon_whenDisabled_isOffState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(false); + when(mHotspotController.isHotspotEnabled()).thenReturn(false); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off)); + } + + @Test + public void testIcon_whenTransient_isSearchState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(true); + when(mHotspotController.isHotspotEnabled()).thenReturn(true); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search)); + } + + @Test + public void testIcon_whenEnabled_isOnState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(false); + when(mHotspotController.isHotspotEnabled()).thenReturn(true); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index ea70c263a121..0c070da1fcb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -21,9 +21,9 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.os.Handler -import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R @@ -51,28 +51,17 @@ import org.mockito.MockitoAnnotations @SmallTest class UiModeNightTileTest : SysuiTestCase() { - @Mock - private lateinit var mockContext: Context - @Mock - private lateinit var uiModeManager: UiModeManager - @Mock - private lateinit var resources: Resources - @Mock - private lateinit var qsLogger: QSLogger - @Mock - private lateinit var qsHost: QSTileHost - @Mock - private lateinit var metricsLogger: MetricsLogger - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var configurationController: ConfigurationController - @Mock - private lateinit var batteryController: BatteryController - @Mock - private lateinit var locationController: LocationController + @Mock private lateinit var mockContext: Context + @Mock private lateinit var uiModeManager: UiModeManager + @Mock private lateinit var resources: Resources + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var qsHost: QSTileHost + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var batteryController: BatteryController + @Mock private lateinit var locationController: LocationController private val uiEventLogger = UiEventLoggerFake() private val falsingManager = FalsingManagerFake() @@ -85,7 +74,7 @@ class UiModeNightTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) configuration = Configuration() - mContext.addMockSystemService(Context.UI_MODE_SERVICE, uiModeManager) + mContext.addMockSystemService(UiModeManager::class.java, uiModeManager) `when`(qsHost.context).thenReturn(mockContext) `when`(qsHost.userContext).thenReturn(mContext) @@ -93,7 +82,8 @@ class UiModeNightTileTest : SysuiTestCase() { `when`(resources.configuration).thenReturn(configuration) `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) - tile = UiModeNightTile( + tile = + UiModeNightTile( qsHost, testableLooper.looper, Handler(testableLooper.looper), @@ -104,7 +94,8 @@ class UiModeNightTileTest : SysuiTestCase() { qsLogger, configurationController, batteryController, - locationController) + locationController + ) } @Test @@ -115,7 +106,7 @@ class UiModeNightTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on)) } @Test @@ -126,7 +117,7 @@ class UiModeNightTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off)) } private fun setNightModeOn() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2adc389a964e..481e4e9992d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -21,12 +21,16 @@ import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent import android.view.ViewGroup import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardHostViewController import com.android.keyguard.LockIconViewController +import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager +import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationShadeDepthController @@ -51,9 +55,9 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +@SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) -@SmallTest class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @@ -72,8 +76,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock + private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var ambientState: AmbientState @Mock + private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @@ -87,6 +95,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory + @Mock lateinit var keyguardBouncerContainer: ViewGroup + @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent + @Mock lateinit var keyguardHostViewController: KeyguardHostViewController private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> private lateinit var interactionEventHandler: InteractionEventHandler @@ -97,7 +109,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) - underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, FalsingCollectorFake(), @@ -115,7 +126,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { notificationShadeWindowController, keyguardUnlockAnimationController, ambientState, - pulsingGestureListener + pulsingGestureListener, + featureFlags, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 001bfeeabe6f..4a7dec912895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -33,11 +33,14 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; import com.android.keyguard.LockIconViewController; +import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -86,6 +89,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private AmbientState mAmbientState; @Mock private PulsingGestureListener mPulsingGestureListener; + @Mock private FeatureFlags mFeatureFlags; + @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; + @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> mInteractionEventHandlerCaptor; @@ -121,7 +127,10 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationShadeWindowController, mKeyguardUnlockAnimationController, mAmbientState, - mPulsingGestureListener + mPulsingGestureListener, + mFeatureFlags, + mKeyguardBouncerViewModel, + mKeyguardBouncerComponentFactory ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 464dfe26ca3e..ec5d0894fbef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -635,6 +635,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void transientIndication_visibleWhenDozing_ignoresPowerPressed() { + createController(); + + mController.setVisible(true); + reset(mRotateTextViewController); + mController.getKeyguardCallback().onBiometricError( + FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo", + BiometricSourceType.FINGERPRINT); + + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + } + + @Test public void transientIndication_swipeUpToRetry() { createController(); String message = mContext.getString(R.string.keyguard_retry); 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 d3c1dc9db218..a95a49c31adf 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 @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -32,8 +34,10 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -57,6 +61,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private Runnable mRoundnessCallback = mock(Runnable.class); private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; + private NotificationRoundnessLogger mLogger = mock(NotificationRoundnessLogger.class); private float mSmallRadiusRatio; @Before @@ -66,7 +71,9 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small) / resources.getDimension(R.dimen.notification_corner_radius); mRoundnessManager = new NotificationRoundnessManager( - new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); + new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext), + mLogger, + mock(DumpManager.class)); allowTestableLooperAsMainThread(); NotificationTestHelper testHelper = new NotificationTestHelper( mContext, @@ -337,6 +344,20 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { Assert.assertTrue(mSecond.isLastInSection()); } + @Test + public void testLoggingOnRoundingUpdate() { + NotificationSection[] sections = new NotificationSection[]{ + createSection(mFirst, mSecond), + createSection(null, null) + }; + mRoundnessManager.updateRoundedChildren(sections); + verify(mLogger).onSectionCornersUpdated(sections, /*anyChanged=*/ true); + verify(mLogger, atLeast(1)).onCornersUpdated(eq(mFirst), anyBoolean(), + anyBoolean(), anyBoolean(), anyBoolean()); + verify(mLogger, atLeast(1)).onCornersUpdated(eq(mSecond), anyBoolean(), + anyBoolean(), anyBoolean(), anyBoolean()); + } + private NotificationSection createSection(ExpandableNotificationRow first, ExpandableNotificationRow last) { NotificationSection section = mock(NotificationSection.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index a4453f854ce5..ee4b9d9c93f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -41,11 +41,17 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; +import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyguard.data.BouncerView; +import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.shade.NotificationPanelViewController; @@ -101,6 +107,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; @Mock private DreamOverlayStateController mDreamOverlayStateController; @Mock private LatencyTracker mLatencyTracker; + @Mock private FeatureFlags mFeatureFlags; + @Mock private KeyguardSecurityModel mKeyguardSecurityModel; + @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor; + @Mock private BouncerInteractor mBouncerInteractor; + @Mock private BouncerView mBouncerView; +// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference; + @Mock private BouncerViewDelegate mBouncerViewDelegate; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback; @@ -115,6 +128,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) .thenReturn(mKeyguardMessageAreaController); + when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate); + mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( getContext(), @@ -133,7 +148,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mKeyguardMessageAreaFactory, Optional.of(mSysUiUnfoldComponent), () -> mShadeController, - mLatencyTracker); + mLatencyTracker, + mKeyguardSecurityModel, + mFeatureFlags, + mBouncerCallbackInteractor, + mBouncerInteractor, + mBouncerView); mStatusBarKeyguardViewManager.registerCentralSurfaces( mCentralSurfaces, mNotificationPanelView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt index 37c0f3621b6f..bf432388ad28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt @@ -34,14 +34,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @SmallTest -class StatusBarUserSwitcherControllerTest : SysuiTestCase() { +class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var tracker: StatusBarUserInfoTracker diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt new file mode 100644 index 000000000000..f3046477f4d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.policy + +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.os.UserHandle +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.lang.ref.WeakReference +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class BaseUserSwitcherAdapterTest : SysuiTestCase() { + + @Mock private lateinit var controller: UserSwitcherController + + private lateinit var underTest: BaseUserSwitcherAdapter + + private lateinit var users: ArrayList<UserRecord> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + + whenever(controller.users).thenAnswer { users } + + underTest = + object : BaseUserSwitcherAdapter(controller) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + return mock() + } + } + } + + @Test + fun `Adds self to controller in constructor`() { + val captor = kotlinArgumentCaptor<WeakReference<BaseUserSwitcherAdapter>>() + verify(controller).addAdapter(captor.capture()) + + assertThat(captor.value.get()).isEqualTo(underTest) + } + + @Test + fun count() { + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun `count - ignores restricted users when device is locked`() { + whenever(controller.isKeyguardShowing).thenReturn(true) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, // this one will be ignored. + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size - 1) + } + + @Test + fun `count - does not ignore restricted users when device is not locked`() { + whenever(controller.isKeyguardShowing).thenReturn(false) + users = + ArrayList( + listOf( + createUserRecord( + id = 0, + picture = mock(), + isSelected = true, + isGuest = false, + isRestricted = false, + ), + createUserRecord( + id = 1, + picture = mock(), + isSelected = false, + isGuest = false, + isRestricted = true, + ), + createUserRecord( + id = UserHandle.USER_NULL, + picture = null, + isSelected = false, + isGuest = true, + ), + ) + ) + assertThat(underTest.count).isEqualTo(users.size) + } + + @Test + fun getItem() { + assertThat((0 until underTest.count).map { position -> underTest.getItem(position) }) + .isEqualTo(users) + } + + @Test + fun getItemId() { + (0 until underTest.count).map { position -> + assertThat(underTest.getItemId(position)).isEqualTo(position) + } + } + + @Test + fun onUserListItemClicked() { + val userRecord = users[users.size / 2] + val dialogShower: UserSwitchDialogController.DialogShower = mock() + + underTest.onUserListItemClicked(userRecord, dialogShower) + + verify(controller).onUserListItemClicked(userRecord, dialogShower) + } + + @Test + fun `getName - non guest - returns real name`() { + val userRecord = + createUserRecord( + id = 1, + picture = mock(), + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(userRecord.info?.name) + } + + @Test + fun `getName - guest and selected - returns exit guest action name`() { + val expected = "Exit guest" + context.orCreateTestableResources.addOverride( + com.android.settingslib.R.string.guest_exit_quick_settings_button, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = true, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo(expected) + } + + @Test + fun `getName - guest and not selected - returns enter guest action name`() { + val expected = "Guest" + context.orCreateTestableResources.addOverride( + com.android.internal.R.string.guest_name, + expected, + ) + + val userRecord = + createUserRecord( + id = 2, + picture = null, + isGuest = true, + isSelected = false, + ) + + assertThat(underTest.getName(context, userRecord)).isEqualTo("Guest") + } + + @Test + fun refresh() { + underTest.refresh() + + verify(controller).refreshUsers(UserHandle.USER_NULL) + } + + private fun createUserRecord( + id: Int, + picture: Bitmap? = null, + isSelected: Boolean = false, + isGuest: Boolean = false, + isAction: Boolean = false, + isRestricted: Boolean = false, + ): UserRecord { + return UserRecord( + info = + if (isAction) { + null + } else { + UserInfo(id, "name$id", 0) + }, + picture = picture, + isCurrent = isSelected, + isGuest = isGuest, + isRestricted = isRestricted, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index fda80a2f9ed8..43d0fe9559b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -42,6 +42,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dump.DumpManager; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -80,6 +81,7 @@ public class BatteryControllerTest extends SysuiTestCase { mPowerManager, mBroadcastDispatcher, mDemoModeController, + mock(DumpManager.class), new Handler(), new Handler()); // Can throw if updateEstimate is called on the main thread diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index b4f3987b2f95..b86ca6fc5375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -38,9 +38,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -102,8 +102,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { ViewUtils.attachView(view) testableLooper.processAllMessages() - `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController) - `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true) + `when`(userSwitcherController.isKeyguardShowing).thenReturn(true) `when`(keyguardStateController.isShowing).thenReturn(true) `when`(keyguardStateController.isKeyguardGoingAway).thenReturn(false) keyguardQsUserSwitchController.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt index 8dcd4bb3b738..76ecc1c7f36d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt @@ -86,7 +86,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class UserSwitcherControllerTest : SysuiTestCase() { +class UserSwitcherControllerOldImplTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityManager: IActivityManager @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @@ -118,7 +118,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { private lateinit var longRunningExecutor: FakeExecutor private lateinit var uiExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake - private lateinit var userSwitcherController: UserSwitcherController + private lateinit var userSwitcherController: UserSwitcherControllerOldImpl private lateinit var picture: Bitmap private val ownerId = UserHandle.USER_SYSTEM private val ownerInfo = UserInfo(ownerId, "Owner", null, @@ -205,7 +205,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { } private fun setupController() { - userSwitcherController = UserSwitcherController( + userSwitcherController = + UserSwitcherControllerOldImpl( mContext, activityManager, userManager, @@ -230,7 +231,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { dumpManager, dialogLaunchAnimator, guestResumeSessionReceiver, - guestResetOrExitSessionReceiver) + guestResetOrExitSessionReceiver + ) userSwitcherController.init(notificationShadeWindowView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 6b466e1ac2d8..6fec343d036c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -60,7 +60,7 @@ class UserRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false)) + whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false)) whenever(controller.isGuestUserAutoCreated).thenReturn(false) whenever(controller.isGuestUserResetting).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java index e47acd8d3834..343437634b29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java @@ -41,6 +41,7 @@ import android.view.DisplayInfo; import android.view.SurfaceHolder; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer; import org.junit.Before; @@ -72,6 +73,8 @@ public class ImageWallpaperTest extends SysuiTestCase { private Bitmap mWallpaperBitmap; @Mock private Handler mHandler; + @Mock + private FeatureFlags mFeatureFlags; private CountDownLatch mEventCountdown; @@ -100,7 +103,7 @@ public class ImageWallpaperTest extends SysuiTestCase { } private ImageWallpaper createImageWallpaper() { - return new ImageWallpaper() { + return new ImageWallpaper(mFeatureFlags) { @Override public Engine onCreateEngine() { return new GLEngine(mHandler) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java new file mode 100644 index 000000000000..93f4f8223955 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpapers.canvas; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.hamcrest.MockitoHamcrest.intThat; + +import android.graphics.Bitmap; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.DisplayInfo; +import android.view.SurfaceHolder; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ImageCanvasWallpaperRendererTest extends SysuiTestCase { + + private static final int MOBILE_DISPLAY_WIDTH = 720; + private static final int MOBILE_DISPLAY_HEIGHT = 1600; + + @Mock + private SurfaceHolder mMockSurfaceHolder; + + @Mock + private DisplayInfo mMockDisplayInfo; + + @Mock + private Bitmap mMockBitmap; + + @Before + public void setUp() throws Exception { + allowTestableLooperAsMainThread(); + MockitoAnnotations.initMocks(this); + } + + private void setDimensions( + int bitmapWidth, int bitmapHeight, + int displayWidth, int displayHeight) { + when(mMockBitmap.getWidth()).thenReturn(bitmapWidth); + when(mMockBitmap.getHeight()).thenReturn(bitmapHeight); + mMockDisplayInfo.logicalWidth = displayWidth; + mMockDisplayInfo.logicalHeight = displayHeight; + } + + private void testMinDimensions( + int bitmapWidth, int bitmapHeight) { + + clearInvocations(mMockSurfaceHolder); + setDimensions(bitmapWidth, bitmapHeight, + ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH, + ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT); + + ImageCanvasWallpaperRenderer renderer = + new ImageCanvasWallpaperRenderer(mMockSurfaceHolder); + renderer.drawFrame(mMockBitmap, true); + + verify(mMockSurfaceHolder, times(1)).setFixedSize( + intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)), + intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT))); + } + + @Test + public void testMinSurface() { + // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT + testMinDimensions(8, 8); + + testMinDimensions(100, 2000); + + testMinDimensions(200, 1); + } + + private void testZeroDimensions(int bitmapWidth, int bitmapHeight) { + + clearInvocations(mMockSurfaceHolder); + setDimensions(bitmapWidth, bitmapHeight, + ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH, + ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT); + + ImageCanvasWallpaperRenderer renderer = + new ImageCanvasWallpaperRenderer(mMockSurfaceHolder); + ImageCanvasWallpaperRenderer spyRenderer = spy(renderer); + spyRenderer.drawFrame(mMockBitmap, true); + + verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt()); + verify(spyRenderer, never()).drawWallpaperWithCanvas(any()); + } + + @Test + public void testZeroBitmap() { + // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0 + testZeroDimensions( + 0, 1 + ); + + testZeroDimensions(1, 0 + ); + + testZeroDimensions(0, 0 + ); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index da33fa62a9ab..cebe946a459c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; @@ -74,16 +75,16 @@ public class WMShellTest extends SysuiTestCase { @Mock ProtoTracer mProtoTracer; @Mock UserTracker mUserTracker; @Mock ShellExecutor mSysUiMainExecutor; + @Mock FloatingTasks mFloatingTasks; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip), - Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue, - mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor, - mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, - mUserTracker, mSysUiMainExecutor); + Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks), + mCommandQueue, mConfigurationController, mKeyguardStateController, + mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, + mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor); } @Test diff --git a/packages/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml index a63f592fa6a7..94a79090bb51 100644 --- a/packages/VpnDialogs/res/values-ro/strings.xml +++ b/packages/VpnDialogs/res/values-ro/strings.xml @@ -26,10 +26,10 @@ <string name="data_received" msgid="4062776929376067820">"Primite:"</string> <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> byți/<xliff:g id="NUMBER_1">%2$s</xliff:g> pachete"</string> <string name="always_on_disconnected_title" msgid="1906740176262776166">"Nu se poate conecta la rețeaua VPN activată permanent"</string> - <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Telefonul dvs. va folosi o rețea publică până când se va putea reconecta la <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string> - <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Nu veți avea conexiune până când se va putea reconecta rețeaua VPN."</string> + <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Telefonul va folosi o rețea publică până când se va putea reconecta la <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string> + <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Nu vei avea conexiune până când se va putea reconecta rețeaua VPN."</string> <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string> - <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Modificați setările VPN"</string> + <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Modifică setările VPN"</string> <string name="configure" msgid="4905518375574791375">"Configurează"</string> <string name="disconnect" msgid="971412338304200056">"Deconectează"</string> <string name="open_app" msgid="3717639178595958667">"Deschide aplicația"</string> diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml index 6e5947c0d753..b9cc0b066dcf 100644 --- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml +++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Redați aplicațiile sub zona de decupaj"</string> + <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Redă aplicațiile sub zona de decupaj"</string> </resources> diff --git a/services/Android.bp b/services/Android.bp index decfa77e002a..672b3450dc1b 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -89,7 +89,6 @@ filegroup { ":services.autofill-sources", ":services.backup-sources", ":backuplib-sources", - ":services.cloudsearch-sources", ":services.companion-sources", ":services.contentcapture-sources", ":services.contentsuggestions-sources", @@ -143,7 +142,6 @@ java_library { "services.appwidget", "services.autofill", "services.backup", - "services.cloudsearch", "services.companion", "services.contentcapture", "services.contentsuggestions", diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 5a35474207f7..cc29109d85c5 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -770,12 +770,14 @@ final class AutofillManagerServiceImpl /** * Updates the last fill selection when an authentication was selected. */ - void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) { + void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState, + int uiType) { synchronized (mLock) { if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null, - null, null, null, null, null, null)); + null, null, null, null, null, null, + NO_SAVE_UI_REASON_NONE, uiType)); } } } @@ -784,12 +786,13 @@ final class AutofillManagerServiceImpl * Updates the last fill selection when an dataset authentication was selected. */ void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, int uiType) { synchronized (mLock) { if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, - clientState, null, null, null, null, null, null, null, null)); + clientState, null, null, null, null, null, null, null, null, + NO_SAVE_UI_REASON_NONE, uiType)); } } } @@ -810,13 +813,13 @@ final class AutofillManagerServiceImpl * Updates the last fill response when a dataset was selected. */ void logDatasetSelected(@Nullable String selectedDataset, int sessionId, - @Nullable Bundle clientState, int presentationType) { + @Nullable Bundle clientState, int uiType) { synchronized (mLock) { if (isValidEventLocked("logDatasetSelected()", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, - presentationType)); + uiType)); } } } @@ -824,13 +827,13 @@ final class AutofillManagerServiceImpl /** * Updates the last fill response when a dataset is shown. */ - void logDatasetShown(int sessionId, @Nullable Bundle clientState, int presentationType) { + void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType) { synchronized (mLock) { if (isValidEventLocked("logDatasetShown", sessionId)) { mEventHistory.addEvent( new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, - presentationType)); + uiType)); } } } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index b5fdaca838df..6bb19ce05813 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -32,6 +32,8 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; @@ -72,19 +74,32 @@ public final class PresentationStatsEventLogger { NOT_SHOWN_REASON_VIEW_CHANGED, NOT_SHOWN_REASON_ACTIVITY_FINISHED, NOT_SHOWN_REASON_REQUEST_TIMEOUT, + NOT_SHOWN_REASON_REQUEST_FAILED, + NOT_SHOWN_REASON_NO_FOCUS, NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY, NOT_SHOWN_REASON_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) public @interface NotShownReason {} - public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; - public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; - public static final int NOT_SHOWN_REASON_VIEW_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; - public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; - public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT; - public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; - public static final int NOT_SHOWN_REASON_UNKNOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; + public static final int NOT_SHOWN_REASON_ANY_SHOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; + public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; + public static final int NOT_SHOWN_REASON_VIEW_CHANGED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; + public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; + public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT; + public static final int NOT_SHOWN_REASON_REQUEST_FAILED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; + public static final int NOT_SHOWN_REASON_NO_FOCUS = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS; + public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; + public static final int NOT_SHOWN_REASON_UNKNOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; private final int mSessionId; private Optional<PresentationStatsEventInternal> mEventInternal; @@ -118,6 +133,14 @@ public final class PresentationStatsEventLogger { }); } + public void maybeSetNoPresentationEventReasonIfNoReasonExists(@NotShownReason int reason) { + mEventInternal.ifPresent(event -> { + if (event.mCountShown == 0 && event.mNoPresentationReason == NOT_SHOWN_REASON_UNKNOWN) { + event.mNoPresentationReason = reason; + } + }); + } + public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList, AutofillId currentViewId) { mEventInternal.ifPresent(event -> { @@ -180,7 +203,8 @@ public final class PresentationStatsEventLogger { public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) { mEventInternal.ifPresent(event -> { - event.mDisplayPresentationType = UI_TYPE_INLINE; + event.mDisplayPresentationType = + AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (TextUtils.isEmpty(imeString)) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d5945a5cb255..47ce5928c0be 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -44,6 +44,8 @@ import static com.android.server.autofill.Helper.getNumericValue; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; import static com.android.server.autofill.Helper.toArray; +import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; +import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_CHANGED; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED; @@ -407,6 +409,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private PresentationStatsEventLogger mPresentationStatsEventLogger; + /** + * Fill dialog request would likely be sent slightly later. + */ + @NonNull + @GuardedBy("mLock") + private boolean mPreviouslyFillDialogPotentiallyStarted; + void onSwitchInputMethodLocked() { // One caveat is that for the case where the focus is on a field for which regular autofill // returns null, and augmented autofill is triggered, and then the user switches the input @@ -1179,6 +1188,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags) { + final AutofillId[] fieldClassificationIds; final LogMaker requestLog; @@ -1357,9 +1367,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - // TODO(b/234185326): Add separate reason for failures. - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_REQUEST_TIMEOUT); + if (timedOut) { + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_REQUEST_TIMEOUT); + } else { + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_REQUEST_FAILED); + } mPresentationStatsEventLogger.logAndEndEvent(); } notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED, @@ -1468,7 +1482,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras, - boolean authenticateInline) { + int uiType) { if (sDebug) { Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex + "; intentSender=" + intent); @@ -1487,12 +1501,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - mService.setAuthenticationSelected(id, mClientState); + mService.setAuthenticationSelected(id, mClientState, uiType); final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); mHandler.sendMessage(obtainMessage( Session::startAuthentication, - this, authenticationId, intent, fillInIntent, authenticateInline)); + this, authenticationId, intent, fillInIntent, + /* authenticateInline= */ uiType == UI_TYPE_INLINE)); } // AutoFillUiCallback @@ -2887,6 +2902,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, @NonNull ViewState viewState, int flags) { + // Force new response for manual request if ((flags & FLAG_MANUAL_REQUEST) != 0) { mSessionFlags.mAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); @@ -2894,7 +2910,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } - // If it's not, then check if it it should start a partition. + // If it's not, then check if it should start a partition. if (shouldStartNewPartitionLocked(id)) { if (sDebug) { Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " @@ -2993,7 +3009,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) { Slog.d(TAG, "Set the response has expired."); } - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( NOT_SHOWN_REASON_VIEW_CHANGED); mPresentationStatsEventLogger.logAndEndEvent(); return; @@ -3038,11 +3054,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // View is triggering autofill. mCurrentViewId = viewState.id; viewState.update(value, virtualBounds, flags); + mPresentationStatsEventLogger.startNewEvent(); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); + mPresentationStatsEventLogger.maybeSetIsNewRequest(true); if (!isRequestSupportFillDialog(flags)) { mSessionFlags.mFillDialogDisabled = true; + mPreviouslyFillDialogPotentiallyStarted = false; + } else { + // Set the default reason for now if the user doesn't trigger any focus event + // on the autofillable view. This can be changed downstream when more + // information is available or session is committed. + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_NO_FOCUS); + mPreviouslyFillDialogPotentiallyStarted = true; } - mPresentationStatsEventLogger.startNewEvent(); - mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); break; case ACTION_VALUE_CHANGED: @@ -3085,6 +3110,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } break; case ACTION_VIEW_ENTERED: + boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted; + mPreviouslyFillDialogPotentiallyStarted = false; if (sVerbose && virtualBounds != null) { Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds); } @@ -3101,9 +3128,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); + // Previously, fill request will only start whenever a view is entered. + // With Fill Dialog, request starts prior to view getting entered. So, we can't end + // the event at this moment, otherwise we will be wrongly attributing fill dialog + // event as concluded. + if (!wasPreviouslyFillDialog) { + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); + mPresentationStatsEventLogger.logAndEndEvent(); + } if ((flags & FLAG_MANUAL_REQUEST) == 0) { // Not a manual request @@ -3131,9 +3164,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - mPresentationStatsEventLogger.startNewEvent(); - mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); + if (!wasPreviouslyFillDialog) { + mPresentationStatsEventLogger.startNewEvent(); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid( + getAutofillServiceUid()); + } if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { + // If a new request was issued even if previously it was fill dialog request, + // we should end the log event, and start a new one. However, it leaves us + // susceptible to race condition. But since mPresentationStatsEventLogger is + // lock guarded, we should be safe. + if (wasPreviouslyFillDialog) { + mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.startNewEvent(); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid( + getAutofillServiceUid()); + } return; } @@ -3369,7 +3415,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestShowInlineSuggestionsLocked(response, filterText)) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_INLINE_SHOWN); - // TODO(b/137800469): Fix it to log showed only when IME asks for inflation, + // TODO(b/248378401): Fix it to log showed only when IME asks for inflation, // rather than here where framework sends back the response. mService.logDatasetShown(id, mClientState, UI_TYPE_INLINE); @@ -3390,7 +3436,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { mService.logDatasetShown(id, mClientState, UI_TYPE_MENU); - mPresentationStatsEventLogger.maybeSetCountShown( response.getDatasets(), mCurrentViewId); mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU); @@ -3547,7 +3592,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void authenticate(int requestId, int datasetIndex) { Session.this.authenticate(response.getRequestId(), datasetIndex, response.getAuthentication(), response.getClientState(), - /* authenticateInline= */ true); + UI_TYPE_INLINE); } @Override @@ -4087,7 +4132,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // ...or handle authentication. - mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState); + mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType); setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState); if (fillInIntent == null) { diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index e07f41204500..5f0f9a3f1577 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -84,7 +84,7 @@ public final class AutoFillUI { public interface AutoFillUiCallback { void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent, - @Nullable Bundle extras, boolean authenticateInline); + @Nullable Bundle extras, int uiType); void fill(int requestId, int datasetIndex, @NonNull Dataset dataset, @FillEventHistory.Event.UiType int uiType); void save(); @@ -232,7 +232,7 @@ public final class AutoFillUI { mCallback.authenticate(response.getRequestId(), AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED, response.getAuthentication(), response.getClientState(), - /* authenticateInline= */ false); + UI_TYPE_MENU); } } @@ -419,7 +419,7 @@ public final class AutoFillUI { mCallback.authenticate(response.getRequestId(), AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED, response.getAuthentication(), response.getClientState(), - /* authenticateInline= */ false); + UI_TYPE_DIALOG); } } diff --git a/services/cloudsearch/Android.bp b/services/cloudsearch/Android.bp deleted file mode 100644 index e38e6153016f..000000000000 --- a/services/cloudsearch/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -filegroup { - name: "services.cloudsearch-sources", - srcs: ["java/**/*.java"], - path: "java", - visibility: ["//frameworks/base/services"], -} - -java_library_static { - name: "services.cloudsearch", - defaults: ["platform_service_defaults"], - srcs: [":services.cloudsearch-sources"], - libs: ["services.core"], -} diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java deleted file mode 100644 index ac2d1dd95da1..000000000000 --- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.cloudsearch; - -import static android.Manifest.permission.MANAGE_CLOUDSEARCH; -import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; -import static android.content.Context.CLOUDSEARCH_SERVICE; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.ActivityManagerInternal; -import android.app.cloudsearch.ICloudSearchManager; -import android.app.cloudsearch.ICloudSearchManagerCallback; -import android.app.cloudsearch.SearchRequest; -import android.app.cloudsearch.SearchResponse; -import android.content.Context; -import android.os.Binder; -import android.os.IBinder; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.util.Slog; - -import com.android.internal.R; -import com.android.server.LocalServices; -import com.android.server.infra.AbstractMasterSystemService; -import com.android.server.infra.FrameworkResourcesServiceNameResolver; -import com.android.server.wm.ActivityTaskManagerInternal; - -import java.io.FileDescriptor; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * A service used to return cloudsearch targets given a query. - */ -public class CloudSearchManagerService extends - AbstractMasterSystemService<CloudSearchManagerService, CloudSearchPerUserService> { - - private static final String TAG = CloudSearchManagerService.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes - - private final ActivityTaskManagerInternal mActivityTaskManagerInternal; - - private final Context mContext; - - public CloudSearchManagerService(Context context) { - super(context, new FrameworkResourcesServiceNameResolver(context, - R.array.config_defaultCloudSearchServices, true), null, - PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH); - mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); - mContext = context; - } - - @Override - protected CloudSearchPerUserService newServiceLocked(int resolvedUserId, boolean disabled) { - return new CloudSearchPerUserService(this, mLock, resolvedUserId, ""); - } - - @Override - protected List<CloudSearchPerUserService> newServiceListLocked(int resolvedUserId, - boolean disabled, String[] serviceNames) { - if (serviceNames == null) { - return new ArrayList<>(); - } - List<CloudSearchPerUserService> serviceList = - new ArrayList<>(serviceNames.length); - for (int i = 0; i < serviceNames.length; i++) { - if (serviceNames[i] == null) { - continue; - } - serviceList.add(new CloudSearchPerUserService(this, mLock, resolvedUserId, - serviceNames[i])); - } - return serviceList; - } - - @Override - public void onStart() { - publishBinderService(CLOUDSEARCH_SERVICE, new CloudSearchManagerStub()); - } - - @Override - protected void enforceCallingPermissionForManagement() { - getContext().enforceCallingPermission(MANAGE_CLOUDSEARCH, TAG); - } - - @Override // from AbstractMasterSystemService - protected void onServicePackageUpdatedLocked(@UserIdInt int userId) { - final CloudSearchPerUserService service = peekServiceForUserLocked(userId); - if (service != null) { - service.onPackageUpdatedLocked(); - } - } - - @Override // from AbstractMasterSystemService - protected void onServicePackageRestartedLocked(@UserIdInt int userId) { - final CloudSearchPerUserService service = peekServiceForUserLocked(userId); - if (service != null) { - service.onPackageRestartedLocked(); - } - } - - @Override - protected int getMaximumTemporaryServiceDurationMs() { - return MAX_TEMP_SERVICE_DURATION_MS; - } - - private class CloudSearchManagerStub extends ICloudSearchManager.Stub { - - @Override - public void search(@NonNull SearchRequest searchRequest, - @NonNull ICloudSearchManagerCallback callBack) { - searchRequest.setCallerPackageName( - mContext.getPackageManager().getNameForUid(Binder.getCallingUid())); - runForUser("search", (service) -> { - synchronized (service.mLock) { - service.onSearchLocked(searchRequest, callBack); - } - }); - } - - @Override - public void returnResults(IBinder token, String requestId, SearchResponse response) { - runForUser("returnResults", (service) -> { - synchronized (service.mLock) { - service.onReturnResultsLocked(token, requestId, response); - } - }); - } - - public void destroy(@NonNull SearchRequest searchRequest) { - runForUser("destroyCloudSearchSession", (service) -> { - synchronized (service.mLock) { - service.onDestroyLocked(searchRequest.getRequestId()); - } - }); - } - - public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, - @Nullable FileDescriptor err, - @NonNull String[] args, @Nullable ShellCallback callback, - @NonNull ResultReceiver resultReceiver) { - new CloudSearchManagerServiceShellCommand(CloudSearchManagerService.this) - .exec(this, in, out, err, args, callback, resultReceiver); - } - - private void runForUser(@NonNull final String func, - @NonNull final Consumer<CloudSearchPerUserService> c) { - ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class); - final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), - Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL, - null, null); - - if (DEBUG) { - Slog.d(TAG, "runForUser:" + func + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - } - Context ctx = getContext(); - if (!(ctx.checkCallingPermission(MANAGE_CLOUDSEARCH) == PERMISSION_GRANTED - || mServiceNameResolver.isTemporary(userId) - || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) { - - String msg = "Permission Denial: Cannot call " + func + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - final List<CloudSearchPerUserService> services = - getServiceListForUserLocked(userId); - for (int i = 0; i < services.size(); i++) { - c.accept(services.get(i)); - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } -} diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java deleted file mode 100644 index c64982d94d6d..000000000000 --- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.cloudsearch; - -import android.annotation.NonNull; -import android.os.ShellCommand; - -import java.io.PrintWriter; - -/** - * The shell command implementation for the CloudSearchManagerService. - */ -public class CloudSearchManagerServiceShellCommand extends ShellCommand { - - private static final String TAG = - CloudSearchManagerServiceShellCommand.class.getSimpleName(); - - private final CloudSearchManagerService mService; - - public CloudSearchManagerServiceShellCommand(@NonNull CloudSearchManagerService service) { - mService = service; - } - - @Override - public int onCommand(String cmd) { - if (cmd == null) { - return handleDefaultCommands(cmd); - } - final PrintWriter pw = getOutPrintWriter(); - switch (cmd) { - case "set": { - final String what = getNextArgRequired(); - switch (what) { - case "temporary-service": { - final int userId = Integer.parseInt(getNextArgRequired()); - String serviceName = getNextArg(); - if (serviceName == null) { - mService.resetTemporaryService(userId); - pw.println("CloudSearchService temporarily reset. "); - return 0; - } - final int duration = Integer.parseInt(getNextArgRequired()); - String[] services = serviceName.split(";"); - if (services.length == 0) { - return 0; - } else { - mService.setTemporaryServices(userId, services, duration); - } - pw.println("CloudSearchService temporarily set to " + serviceName - + " for " + duration + "ms"); - break; - } - } - } - break; - default: - return handleDefaultCommands(cmd); - } - return 0; - } - - @Override - public void onHelp() { - try (PrintWriter pw = getOutPrintWriter()) { - pw.println("CloudSearchManagerService commands:"); - pw.println(" help"); - pw.println(" Prints this help text."); - pw.println(""); - pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]"); - pw.println(" Temporarily (for DURATION ms) changes the service implemtation."); - pw.println(" To reset, call with just the USER_ID argument."); - pw.println(""); - } - } -} diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java deleted file mode 100644 index 222d779f20e8..000000000000 --- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.cloudsearch; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.AppGlobals; -import android.app.cloudsearch.ICloudSearchManagerCallback; -import android.app.cloudsearch.SearchRequest; -import android.app.cloudsearch.SearchResponse; -import android.content.ComponentName; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; -import android.service.cloudsearch.CloudSearchService; -import android.service.cloudsearch.ICloudSearchService; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AbstractRemoteService; -import com.android.server.CircularQueue; -import com.android.server.infra.AbstractPerUserSystemService; - -/** - * Per-user instance of {@link CloudSearchManagerService}. - */ -public class CloudSearchPerUserService extends - AbstractPerUserSystemService<CloudSearchPerUserService, CloudSearchManagerService> - implements RemoteCloudSearchService.RemoteCloudSearchServiceCallbacks { - - private static final String TAG = CloudSearchPerUserService.class.getSimpleName(); - private static final int QUEUE_SIZE = 10; - @GuardedBy("mLock") - private final CircularQueue<String, CloudSearchCallbackInfo> mCallbackQueue = - new CircularQueue<>(QUEUE_SIZE); - private final String mServiceName; - private final ComponentName mRemoteComponentName; - @Nullable - @GuardedBy("mLock") - private RemoteCloudSearchService mRemoteService; - /** - * When {@code true}, remote service died but service state is kept so it's restored after - * the system re-binds to it. - */ - @GuardedBy("mLock") - private boolean mZombie; - - protected CloudSearchPerUserService(CloudSearchManagerService master, - Object lock, int userId, String serviceName) { - super(master, lock, userId); - mServiceName = serviceName; - mRemoteComponentName = ComponentName.unflattenFromString(mServiceName); - } - - @Override // from PerUserSystemService - protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) - throws NameNotFoundException { - - ServiceInfo si; - try { - si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, - PackageManager.GET_META_DATA, mUserId); - } catch (RemoteException e) { - throw new NameNotFoundException("Could not get service for " + serviceComponent); - } - // TODO(b/177858728): must check that either the service is from a system component, - // or it matches a service set by shell cmd (so it can be used on CTS tests and when - // OEMs are implementing the real service and also verify the proper permissions - return si; - } - - @GuardedBy("mLock") - @Override // from PerUserSystemService - protected boolean updateLocked(boolean disabled) { - final boolean enabledChanged = super.updateLocked(disabled); - if (enabledChanged) { - if (isEnabledLocked()) { - // Send the pending sessions over to the service - resurrectSessionsLocked(); - } else { - // Clear the remote service for the next call - updateRemoteServiceLocked(); - } - } - return enabledChanged; - } - - /** - * Notifies the service of a new cloudsearch session. - */ - @GuardedBy("mLock") - public void onSearchLocked(@NonNull SearchRequest searchRequest, - @NonNull ICloudSearchManagerCallback callback) { - if (mRemoteComponentName == null) { - return; - } - - String filterList = searchRequest.getSearchConstraints().containsKey( - SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER) - ? searchRequest.getSearchConstraints().getString( - SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER) : ""; - - String remoteServicePackageName = mRemoteComponentName.getPackageName(); - // By default, all providers are marked as wanted. - boolean wantedProvider = true; - if (filterList.length() > 0) { - // If providers are specified by the client, - wantedProvider = false; - String[] providersSpecified = filterList.split(";"); - for (int i = 0; i < providersSpecified.length; i++) { - if (providersSpecified[i].equals(remoteServicePackageName)) { - wantedProvider = true; - break; - } - } - } - // If the provider was not requested by the Client, the request will not be sent to the - // provider. - if (!wantedProvider) { - // TODO(216520546) Send a failure callback to the client. - return; - } - final boolean serviceExists = resolveService(searchRequest, - s -> s.onSearch(searchRequest)); - String requestId = searchRequest.getRequestId(); - if (serviceExists && !mCallbackQueue.containsKey(requestId)) { - final CloudSearchCallbackInfo sessionInfo = new CloudSearchCallbackInfo( - requestId, searchRequest, callback, callback.asBinder(), () -> { - synchronized (mLock) { - onDestroyLocked(requestId); - } - }); - if (sessionInfo.linkToDeath()) { - CloudSearchCallbackInfo removedInfo = mCallbackQueue.put(requestId, sessionInfo); - if (removedInfo != null) { - removedInfo.destroy(); - } - } else { - // destroy the session if calling process is already dead - onDestroyLocked(requestId); - } - } - } - - /** - * Used to return results back to the clients. - */ - @GuardedBy("mLock") - public void onReturnResultsLocked(@NonNull IBinder token, - @NonNull String requestId, - @NonNull SearchResponse response) { - if (mRemoteService == null) { - return; - } - ICloudSearchService serviceInterface = mRemoteService.getServiceInterface(); - if (serviceInterface == null || token != serviceInterface.asBinder()) { - return; - } - if (mCallbackQueue.containsKey(requestId)) { - response.setSource(mServiceName); - final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.getElement(requestId); - try { - if (response.getStatusCode() == SearchResponse.SEARCH_STATUS_OK) { - sessionInfo.mCallback.onSearchSucceeded(response); - } else { - sessionInfo.mCallback.onSearchFailed(response); - } - } catch (RemoteException e) { - if (mMaster.debug) { - Slog.e(TAG, "Exception in posting results"); - e.printStackTrace(); - } - onDestroyLocked(requestId); - } - } - } - - /** - * Notifies the server about the end of an existing cloudsearch session. - */ - @GuardedBy("mLock") - public void onDestroyLocked(@NonNull String requestId) { - if (isDebug()) { - Slog.d(TAG, "onDestroyLocked(): requestId=" + requestId); - } - final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.removeElement(requestId); - if (sessionInfo != null) { - sessionInfo.destroy(); - } - } - - @Override - public void onFailureOrTimeout(boolean timedOut) { - if (isDebug()) { - Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut); - } - // Do nothing, we are just proxying to the cloudsearch service - } - - @Override - public void onConnectedStateChanged(boolean connected) { - if (isDebug()) { - Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected); - } - if (connected) { - synchronized (mLock) { - if (mZombie) { - // Validation check - shouldn't happen - if (mRemoteService == null) { - Slog.w(TAG, "Cannot resurrect sessions because remote service is null"); - return; - } - mZombie = false; - resurrectSessionsLocked(); - } - } - } - } - - @Override - public void onServiceDied(RemoteCloudSearchService service) { - if (isDebug()) { - Slog.w(TAG, "onServiceDied(): service=" + service); - } - synchronized (mLock) { - mZombie = true; - } - updateRemoteServiceLocked(); - } - - @GuardedBy("mLock") - private void updateRemoteServiceLocked() { - if (mRemoteService != null) { - mRemoteService.destroy(); - mRemoteService = null; - } - } - - void onPackageUpdatedLocked() { - if (isDebug()) { - Slog.v(TAG, "onPackageUpdatedLocked()"); - } - destroyAndRebindRemoteService(); - } - - void onPackageRestartedLocked() { - if (isDebug()) { - Slog.v(TAG, "onPackageRestartedLocked()"); - } - destroyAndRebindRemoteService(); - } - - private void destroyAndRebindRemoteService() { - if (mRemoteService == null) { - return; - } - - if (isDebug()) { - Slog.d(TAG, "Destroying the old remote service."); - } - mRemoteService.destroy(); - mRemoteService = null; - - synchronized (mLock) { - mZombie = true; - } - mRemoteService = getRemoteServiceLocked(); - if (mRemoteService != null) { - if (isDebug()) { - Slog.d(TAG, "Rebinding to the new remote service."); - } - mRemoteService.reconnect(); - } - } - - /** - * Called after the remote service connected, it's used to restore state from a 'zombie' - * service (i.e., after it died). - */ - private void resurrectSessionsLocked() { - final int numCallbacks = mCallbackQueue.size(); - if (isDebug()) { - Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on " - + numCallbacks + " requests."); - } - - for (CloudSearchCallbackInfo callbackInfo : mCallbackQueue.values()) { - callbackInfo.resurrectSessionLocked(this, callbackInfo.mToken); - } - } - - @GuardedBy("mLock") - @Nullable - protected boolean resolveService( - @NonNull final SearchRequest requestId, - @NonNull final AbstractRemoteService.AsyncRequest<ICloudSearchService> cb) { - - final RemoteCloudSearchService service = getRemoteServiceLocked(); - if (service != null) { - service.executeOnResolvedService(cb); - } - return service != null; - } - - @GuardedBy("mLock") - @Nullable - private RemoteCloudSearchService getRemoteServiceLocked() { - if (mRemoteService == null) { - final String serviceName = getComponentNameForMultipleLocked(mServiceName); - if (serviceName == null) { - if (mMaster.verbose) { - Slog.v(TAG, "getRemoteServiceLocked(): not set"); - } - return null; - } - ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); - - mRemoteService = new RemoteCloudSearchService(getContext(), - CloudSearchService.SERVICE_INTERFACE, serviceComponent, mUserId, this, - mMaster.isBindInstantServiceAllowed(), mMaster.verbose); - } - - return mRemoteService; - } - - private static final class CloudSearchCallbackInfo { - private static final boolean DEBUG = false; // Do not submit with true - @NonNull - final IBinder mToken; - @NonNull - final IBinder.DeathRecipient mDeathRecipient; - @NonNull - private final String mRequestId; - @NonNull - private final SearchRequest mSearchRequest; - private final ICloudSearchManagerCallback mCallback; - - CloudSearchCallbackInfo( - @NonNull final String id, - @NonNull final SearchRequest request, - @NonNull final ICloudSearchManagerCallback callback, - @NonNull final IBinder token, - @NonNull final IBinder.DeathRecipient deathRecipient) { - if (DEBUG) { - Slog.d(TAG, "Creating CloudSearchSessionInfo for session Id=" + id); - } - mRequestId = id; - mSearchRequest = request; - mCallback = callback; - mToken = token; - mDeathRecipient = deathRecipient; - } - - boolean linkToDeath() { - try { - mToken.linkToDeath(mDeathRecipient, 0); - } catch (RemoteException e) { - if (DEBUG) { - Slog.w(TAG, "Caller is dead before session can be started, requestId: " - + mRequestId); - } - return false; - } - return true; - } - - void destroy() { - if (DEBUG) { - Slog.d(TAG, "Removing callback for Request Id=" + mRequestId); - } - if (mToken != null) { - mToken.unlinkToDeath(mDeathRecipient, 0); - } - mCallback.asBinder().unlinkToDeath(mDeathRecipient, 0); - } - - void resurrectSessionLocked(CloudSearchPerUserService service, IBinder token) { - if (DEBUG) { - Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked() - + ") for request Id=" + mRequestId); - } - service.onSearchLocked(mSearchRequest, mCallback); - } - } -} diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java b/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java deleted file mode 100644 index d1c0482b4be6..000000000000 --- a/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.cloudsearch; - -import android.annotation.NonNull; -import android.content.ComponentName; -import android.content.Context; -import android.os.IBinder; -import android.service.cloudsearch.ICloudSearchService; -import android.text.format.DateUtils; - -import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; - - -/** - * Proxy to the {@link android.service.cloudsearch.CloudSearchService} implementation in another - * process. - */ -public class RemoteCloudSearchService extends - AbstractMultiplePendingRequestsRemoteService<RemoteCloudSearchService, - ICloudSearchService> { - - private static final String TAG = "RemoteCloudSearchService"; - - private static final long TIMEOUT_IDLE_BOUND_TIMEOUT_MS = 10 * DateUtils.MINUTE_IN_MILLIS; - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; - - private final RemoteCloudSearchServiceCallbacks mCallback; - - public RemoteCloudSearchService(Context context, String serviceInterface, - ComponentName componentName, int userId, - RemoteCloudSearchServiceCallbacks callback, boolean bindInstantServiceAllowed, - boolean verbose) { - super(context, serviceInterface, componentName, userId, callback, - context.getMainThreadHandler(), - bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, - verbose, /* initialCapacity= */ 1); - mCallback = callback; - } - - @Override - protected ICloudSearchService getServiceInterface(IBinder service) { - return ICloudSearchService.Stub.asInterface(service); - } - - @Override - protected long getTimeoutIdleBindMillis() { - return TIMEOUT_IDLE_BOUND_TIMEOUT_MS; - } - - @Override - protected long getRemoteRequestMillis() { - return TIMEOUT_REMOTE_REQUEST_MILLIS; - } - - /** - * Schedules a request to bind to the remote service. - */ - public void reconnect() { - super.scheduleBind(); - } - - /** - * Schedule async request on remote service. - */ - public void scheduleOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) { - scheduleAsyncRequest(request); - } - - /** - * Execute async request on remote service immediately instead of sending it to Handler queue. - */ - public void executeOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) { - executeAsyncRequest(request); - } - - /** - * Failure callback - */ - public interface RemoteCloudSearchServiceCallbacks - extends VultureCallback<RemoteCloudSearchService> { - - /** - * Notifies a the failure or timeout of a remote call. - */ - void onFailureOrTimeout(boolean timedOut); - - /** - * Notifies change in connected state of the remote service. - */ - void onConnectedStateChanged(boolean connected); - } - - @Override // from AbstractRemoteService - protected void handleOnConnectedStateChanged(boolean connected) { - if (mCallback != null) { - mCallback.onConnectedStateChanged(connected); - } - } -} diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java index ec0da490adcf..2904f28fca01 100644 --- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java +++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java @@ -22,14 +22,19 @@ import android.annotation.NonNull; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraInjectionSession; import android.hardware.camera2.CameraManager; +import android.os.Process; +import android.os.UserManager; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.util.List; import java.util.Set; /** @@ -45,6 +50,7 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen private final CameraAccessBlockedCallback mBlockedCallback; private final CameraManager mCameraManager; private final PackageManager mPackageManager; + private final UserManager mUserManager; @GuardedBy("mLock") private int mObserverCount = 0; @@ -66,7 +72,7 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen static class OpenCameraInfo { public String packageName; - public int packageUid; + public Set<Integer> packageUids; } interface CameraAccessBlockedCallback { @@ -85,6 +91,7 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen mBlockedCallback = blockedCallback; mCameraManager = mContext.getSystemService(CameraManager.class); mPackageManager = mContext.getPackageManager(); + mUserManager = mContext.getSystemService(UserManager.class); } /** @@ -125,16 +132,18 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) { final String cameraId = mAppsToBlockOnVirtualDevice.keyAt(i); final OpenCameraInfo openCameraInfo = mAppsToBlockOnVirtualDevice.get(cameraId); - int packageUid = openCameraInfo.packageUid; - if (runningUids.contains(packageUid)) { - final String packageName = openCameraInfo.packageName; - InjectionSessionData data = mPackageToSessionData.get(packageName); - if (data == null) { - data = new InjectionSessionData(); - data.appUid = packageUid; - mPackageToSessionData.put(packageName, data); + final String packageName = openCameraInfo.packageName; + for (int packageUid : openCameraInfo.packageUids) { + if (runningUids.contains(packageUid)) { + InjectionSessionData data = mPackageToSessionData.get(packageName); + if (data == null) { + data = new InjectionSessionData(); + data.appUid = packageUid; + mPackageToSessionData.put(packageName, data); + } + startBlocking(packageName, cameraId); + break; } - startBlocking(packageName, cameraId); } } } @@ -155,37 +164,41 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen @Override public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { synchronized (mLock) { - try { - final ApplicationInfo ainfo = mPackageManager.getApplicationInfo(packageName, 0); - InjectionSessionData data = mPackageToSessionData.get(packageName); - if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) { - OpenCameraInfo openCameraInfo = new OpenCameraInfo(); - openCameraInfo.packageName = packageName; - openCameraInfo.packageUid = ainfo.uid; - mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo); - CameraInjectionSession existingSession = - (data != null) ? data.cameraIdToSession.get(cameraId) : null; - if (existingSession != null) { - existingSession.close(); - data.cameraIdToSession.remove(cameraId); - if (data.cameraIdToSession.isEmpty()) { - mPackageToSessionData.remove(packageName); - } + InjectionSessionData data = mPackageToSessionData.get(packageName); + List<UserInfo> aliveUsers = mUserManager.getAliveUsers(); + ArraySet<Integer> packageUids = new ArraySet<>(); + for (UserInfo user : aliveUsers) { + int userId = user.getUserHandle().getIdentifier(); + int appUid = queryUidFromPackageName(userId, packageName); + if (mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) { + if (data == null) { + data = new InjectionSessionData(); + data.appUid = appUid; + mPackageToSessionData.put(packageName, data); + } + if (data.cameraIdToSession.containsKey(cameraId)) { + return; } + startBlocking(packageName, cameraId); return; + } else { + if (appUid != Process.INVALID_UID) { + packageUids.add(appUid); + } } - if (data == null) { - data = new InjectionSessionData(); - data.appUid = ainfo.uid; - mPackageToSessionData.put(packageName, data); - } - if (data.cameraIdToSession.containsKey(cameraId)) { - return; + } + OpenCameraInfo openCameraInfo = new OpenCameraInfo(); + openCameraInfo.packageName = packageName; + openCameraInfo.packageUids = packageUids; + mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo); + CameraInjectionSession existingSession = + (data != null) ? data.cameraIdToSession.get(cameraId) : null; + if (existingSession != null) { + existingSession.close(); + data.cameraIdToSession.remove(cameraId); + if (data.cameraIdToSession.isEmpty()) { + mPackageToSessionData.remove(packageName); } - startBlocking(packageName, cameraId); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e); - return; } } } @@ -274,4 +287,16 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen } } } + + private int queryUidFromPackageName(int userId, String packageName) { + try { + final ApplicationInfo ainfo = + mPackageManager.getApplicationInfoAsUser(packageName, + PackageManager.GET_ACTIVITIES, userId); + return ainfo.uid; + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "queryUidFromPackageName - unknown package " + packageName, e); + return Process.INVALID_UID; + } + } } diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp index 434f239dbddc..5392c2cde3b8 100644 --- a/services/contentcapture/Android.bp +++ b/services/contentcapture/Android.bp @@ -17,6 +17,9 @@ filegroup { java_library_static { name: "services.contentcapture", defaults: ["platform_service_defaults"], - srcs: [":services.contentcapture-sources"], + srcs: [ + ":services.contentcapture-sources", + "java/**/*.logtags", + ], libs: ["services.core"], } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 7a95a8fe8c7f..a08687f86719 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -60,6 +60,7 @@ import android.service.contentcapture.SnapshotData; import android.service.voice.VoiceInteractionManagerInternal; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -69,6 +70,7 @@ import android.view.contentcapture.DataShareRequest; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; @@ -88,6 +90,10 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); + private static final int EVENT_LOG_CONNECT_STATE_DIED = 0; + static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1; + static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2; + @GuardedBy("mLock") private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); @@ -190,9 +196,12 @@ final class ContentCapturePerUserService Slog.w(TAG, "remote service died: " + service); synchronized (mLock) { mZombie = true; + ComponentName serviceComponent = getServiceComponentName(); writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED, - getServiceComponentName()); + serviceComponent); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId, + EVENT_LOG_CONNECT_STATE_DIED, 0); } } @@ -529,6 +538,15 @@ final class ContentCapturePerUserService return mConditionsByPkg.get(packageName); } + @Nullable + ArraySet<String> getContentCaptureAllowlist() { + ArraySet<String> allowPackages; + synchronized (mLock) { + allowPackages = mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + } + return allowPackages; + } + @GuardedBy("mLock") void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) { if (mRemoteService == null) { @@ -617,8 +635,12 @@ final class ContentCapturePerUserService ArraySet<String> oldList = mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, + CollectionUtils.size(oldList)); mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); + EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId, + CollectionUtils.size(packages), CollectionUtils.size(activities)); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); updateContentCaptureOptions(oldList); @@ -699,13 +721,15 @@ final class ContentCapturePerUserService private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions .getWhitelistedPackages(mUserId); + int addingCount = CollectionUtils.size(adding); + EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, addingCount); if (oldList != null && adding != null) { adding.removeAll(oldList); } - int N = adding != null ? adding.size() : 0; - for (int i = 0; i < N; i++) { + EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount); + for (int i = 0; i < addingCount; i++) { String packageName = adding.valueAt(i); ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions .getOptions(mUserId, packageName); diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags new file mode 100644 index 000000000000..5218b26397df --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags @@ -0,0 +1,13 @@ +# See system/logging/logcat/event.logtags for a description of the format of this file. + +option java_package com.android.server.contentcapture + +# ContentCaptureService connection state change, refer to ContentCapturePerUserService +# for type definition +53200 cc_connect_state_changed (user|1|5),(type|1|5),(package_count|1|1) +# Set the package and activity allowlist +53201 cc_set_allowlist (user|1|5),(package_count|1|1),(activity_count|1|1) +# Get the current allowlist +53202 cc_current_allowlist (user|1|5),(count|1|1) +# update content capture client option with new allow list count +53203 cc_update_options (user|1|5),(count|1) diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 1efe55aa0767..3907de4d903a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -31,6 +31,7 @@ import android.service.contentcapture.IContentCaptureService; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.SnapshotData; +import android.util.EventLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.DataRemovalRequest; @@ -38,6 +39,7 @@ import android.view.contentcapture.DataShareRequest; import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; import com.android.internal.os.IResultReceiver; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; final class RemoteContentCaptureService @@ -88,6 +90,10 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED, mComponentName); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, + mPerUserService.getUserId(), + ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED, + CollectionUtils.size(mPerUserService.getContentCaptureAllowlist())); } finally { // Update the system-service state, in case the service reconnected after // dying @@ -98,6 +104,9 @@ final class RemoteContentCaptureService writeServiceEvent( FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED, mComponentName); + EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, + mPerUserService.getUserId(), + ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED, 0); } } catch (Exception e) { Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e); diff --git a/services/core/Android.bp b/services/core/Android.bp index cbd3f6087df2..a4fc16056ea5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -176,6 +176,13 @@ java_library_static { "ImmutabilityAnnotation", ], javac_shard_size: 50, + javacflags: [ + "-J--add-modules=jdk.compiler", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], } java_genrule { diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index b96d33c0f44b..4278b3edc3eb 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -22,9 +22,12 @@ import static com.android.server.health.Utils.copyV1Battery; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.health.HealthInfo; import android.hardware.health.V2_1.BatteryCapacityLevel; @@ -185,6 +188,17 @@ public final class BatteryService extends SystemService { private ArrayDeque<Bundle> mBatteryLevelsEventQueue; private long mLastBatteryLevelChangedSentMs; + private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle(); + private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle(); + private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle(); + private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle(); + private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle(); + private MetricsLogger mMetricsLogger; public BatteryService(Context context) { @@ -606,7 +620,8 @@ public final class BatteryService extends SystemService { mHandler.post(new Runnable() { @Override public void run() { - mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, + mPowerConnectedOptions); } }); } @@ -617,7 +632,8 @@ public final class BatteryService extends SystemService { mHandler.post(new Runnable() { @Override public void run() { - mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, + mPowerDisconnectedOptions); } }); } @@ -630,7 +646,8 @@ public final class BatteryService extends SystemService { mHandler.post(new Runnable() { @Override public void run() { - mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, + mBatteryLowOptions); } }); } else if (mSentLowBatteryBroadcast && @@ -642,7 +659,8 @@ public final class BatteryService extends SystemService { mHandler.post(new Runnable() { @Override public void run() { - mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, + mBatteryOkayOptions); } }); } @@ -712,7 +730,8 @@ public final class BatteryService extends SystemService { + ", info:" + mHealthInfo.toString()); } - mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL)); + mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, AppOpsManager.OP_NONE, + mBatteryChangedOptions, UserHandle.USER_ALL)); } private void sendBatteryLevelChangedIntentLocked() { diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5c84a628c00d..ae65dcb4d714 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -1,6 +1,9 @@ { "presubmit": [ { + "name": "CtsLocationFineTestCases" + }, + { "name": "CtsLocationCoarseTestCases" }, { @@ -66,10 +69,5 @@ ], "file_patterns": ["ClipboardService\\.java"] } - ], - "postsubmit": [ - { - "name": "CtsLocationFineTestCases" - } ] } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index f26d9f9f49e6..76cac934fdfe 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -24,6 +24,7 @@ import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; +import static android.telephony.SubscriptionManager.isValidSubscriptionId; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -167,6 +168,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { static final String VCN_CONFIG_FILE = new File(Environment.getDataSystemDirectory(), "vcn/configs.xml").getPath(); + // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); + /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; @@ -360,15 +365,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { - // Always run on the handler thread to ensure consistency. - mHandler.post(() -> { - mNetworkProvider.register(); - mContext.getSystemService(ConnectivityManager.class) - .registerNetworkCallback( - new NetworkRequest.Builder().clearCapabilities().build(), - mTrackingNetworkCallback); - mTelephonySubscriptionTracker.register(); - }); + mNetworkProvider.register(); + mContext.getSystemService(ConnectivityManager.class) + .registerNetworkCallback( + new NetworkRequest.Builder().clearCapabilities().build(), + mTrackingNetworkCallback); + mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { @@ -509,15 +511,22 @@ public class VcnManagementService extends IVcnManagementService.Stub { if (!mVcns.containsKey(subGrp)) { startVcnLocked(subGrp, entry.getValue()); } + + // Cancel any scheduled teardowns for active subscriptions + mHandler.removeCallbacksAndMessages(mVcns.get(subGrp)); } } - // Schedule teardown of any VCN instances that have lost carrier privileges + // Schedule teardown of any VCN instances that have lost carrier privileges (after a + // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final ParcelUuid subGrp = entry.getKey(); final VcnConfig config = mConfigs.get(subGrp); final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot); + final boolean isValidActiveDataSubIdNotInVcnSubGrp = + isValidSubscriptionId(snapshot.getActiveDataSubscriptionId()) + && !isActiveSubGroup(subGrp, snapshot); // TODO(b/193687515): Support multiple VCNs active at the same time if (config == null @@ -527,12 +536,31 @@ public class VcnManagementService extends IVcnManagementService.Stub { final ParcelUuid uuidToTeardown = subGrp; final Vcn instanceToTeardown = entry.getValue(); - stopVcnLocked(uuidToTeardown); - - // TODO(b/181789060): invoke asynchronously after Vcn notifies - // through VcnCallback - notifyAllPermissionedStatusCallbacksLocked( - uuidToTeardown, VCN_STATUS_CODE_INACTIVE); + // TODO(b/193687515): Support multiple VCNs active at the same time + // If directly switching to a subscription not in the current group, + // teardown immediately to prevent other subscription's network from being + // outscored by the VCN. Otherwise, teardown after a delay to ensure that + // SIM profile switches do not trigger the VCN to cycle. + final long teardownDelayMs = + isValidActiveDataSubIdNotInVcnSubGrp + ? 0 + : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS; + mHandler.postDelayed(() -> { + synchronized (mLock) { + // Guard against case where this is run after a old instance was + // torn down, and a new instance was started. Verify to ensure + // correct instance is torn down. This could happen as a result of a + // Carrier App manually removing/adding a VcnConfig. + if (mVcns.get(uuidToTeardown) == instanceToTeardown) { + stopVcnLocked(uuidToTeardown); + + // TODO(b/181789060): invoke asynchronously after Vcn notifies + // through VcnCallback + notifyAllPermissionedStatusCallbacksLocked( + uuidToTeardown, VCN_STATUS_CODE_INACTIVE); + } + } + }, instanceToTeardown, teardownDelayMs); } else { // If this VCN's status has not changed, update it with the new snapshot entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7be3dca1992f..d98dffbe1f56 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2440,7 +2440,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (mEnableModernQueue) { mBroadcastQueues = new BroadcastQueue[1]; - mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, foreConstants); + mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, + foreConstants, backConstants); } else { mBroadcastQueues = new BroadcastQueue[4]; mBroadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(this, mHandler, @@ -4514,7 +4515,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Clean-up disabled broadcast receivers. for (int i = mBroadcastQueues.length - 1; i >= 0; i--) { mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked( - packageName, disabledClasses, userId, true); + packageName, disabledClasses, userId); } } @@ -4523,7 +4524,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean didSomething = false; for (int i = mBroadcastQueues.length - 1; i >= 0; i--) { didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked( - null, null, userId, true); + null, null, userId); } return didSomething; } @@ -4659,7 +4660,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (doit) { for (i = mBroadcastQueues.length - 1; i >= 0; i--) { didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked( - packageName, null, userId, doit); + packageName, null, userId); } } @@ -6557,6 +6558,10 @@ public class ActivityManagerService extends IActivityManager.Stub } } + void appNotResponding(@NonNull ProcessRecord anrProcess, @NonNull TimeoutRecord timeoutRecord) { + mAnrHelper.appNotResponding(anrProcess, timeoutRecord); + } + void startPersistentApps(int matchFlags) { if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return; @@ -8927,6 +8932,7 @@ public class ActivityManagerService extends IActivityManager.Stub * @param incrementalMetrics metrics for apps installed on Incremental. * @param errorId a unique id to append to the dropbox headers. */ + @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to worker.run() public void addErrorToDropBox(String eventType, ProcessRecord process, String processName, String activityShortComponentName, String parentShortComponentName, ProcessRecord parentProcess, @@ -17349,6 +17355,8 @@ public class ActivityManagerService extends IActivityManager.Stub bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(), TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, PowerExemptionManager.REASON_LOCALE_CHANGED, ""); + bOptions.setRemoveMatchingFilter( + new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 3299ee3f65d9..ceff67e168ab 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -44,6 +44,7 @@ import static com.android.server.am.ActivityManagerService.appendMemInfo; import static com.android.server.am.ActivityManagerService.getKsmInfo; import static com.android.server.am.ActivityManagerService.stringifyKBSize; import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING; +import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD; @@ -1047,17 +1048,7 @@ public class AppProfiler { } trimMemoryUiHiddenIfNecessaryLSP(app); if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) { - if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, - "Trimming memory of " + app.processName - + " to " + curLevel[0]); - } - thread.scheduleTrimMemory(curLevel[0]); - } catch (RemoteException e) { - } - } + scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of "); profile.setTrimMemoryLevel(curLevel[0]); step[0]++; if (step[0] >= actualFactor) { @@ -1073,31 +1064,11 @@ public class AppProfiler { } } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT && !app.isKilledByAm()) { - if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND - && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, - "Trimming memory of heavy-weight " + app.processName - + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); - } - thread.scheduleTrimMemory( - ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); - } catch (RemoteException e) { - } - } + scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, + "Trimming memory of heavy-weight "); profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); } else { - if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName - + " to " + fgTrimLevel); - } - thread.scheduleTrimMemory(fgTrimLevel); - } catch (RemoteException e) { - } - } + scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg "); profile.setTrimMemoryLevel(fgTrimLevel); } }); @@ -1128,19 +1099,25 @@ public class AppProfiler { // If this application is now in the background and it // had done UI, then give it the special trim level to // have it free UI resources. - final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; - IApplicationThread thread; - if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { - Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " - + app.processName + " to " + level); - } - thread.scheduleTrimMemory(level); - } catch (RemoteException e) { + scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, + "Trimming memory of bg-ui "); + app.mProfile.setPendingUiClean(false); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) { + IApplicationThread thread; + if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level); } + mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, + OOM_ADJ_REASON_NONE); + thread.scheduleTrimMemory(level); + } catch (RemoteException e) { } - app.mProfile.setPendingUiClean(false); } } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 2ebe0b48296b..3efb628a8b75 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.annotation.IntDef; +import android.annotation.NonNull; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.compat.annotation.Overridable; @@ -24,6 +25,8 @@ import android.content.ContentResolver; import android.database.ContentObserver; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.KeyValueListParser; import android.util.Slog; @@ -39,6 +42,9 @@ import java.lang.annotation.RetentionPolicy; public class BroadcastConstants { private static final String TAG = "BroadcastConstants"; + // TODO: migrate remaining constants to be loaded from DeviceConfig + // TODO: migrate fg/bg values into single constants instance + // Value element names within the Settings record static final String KEY_TIMEOUT = "bcast_timeout"; static final String KEY_SLOW_TIME = "bcast_slow_time"; @@ -115,6 +121,35 @@ public class BroadcastConstants { // started its process can start a background activity. public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT; + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to + * dispatch broadcasts to simultaneously. + */ + public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES; + private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4; + + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts + * to dispatch to a "running" process queue before we retire them back to + * being "runnable" to give other processes a chance to run. + */ + public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS; + private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16; + + /** + * For {@link BroadcastQueueModernImpl}: Default delay to apply to normal + * broadcasts, giving a chance for debouncing of rapidly changing events. + */ + public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS; + private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER; + + /** + * For {@link BroadcastQueueModernImpl}: Default delay to apply to + * broadcasts targeting cached applications. + */ + public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS; + private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER; + // Settings override tracking for this instance private String mSettingsKey; private SettingsObserver mSettingsObserver; @@ -128,7 +163,7 @@ public class BroadcastConstants { @Override public void onChange(boolean selfChange) { - updateConstants(); + updateSettingsConstants(); } } @@ -148,11 +183,15 @@ public class BroadcastConstants { mSettingsObserver = new SettingsObserver(handler); mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey), false, mSettingsObserver); + updateSettingsConstants(); - updateConstants(); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + new HandlerExecutor(handler), this::updateDeviceConfigConstants); + updateDeviceConfigConstants( + DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER)); } - private void updateConstants() { + private void updateSettingsConstants() { synchronized (mParser) { try { mParser.setString(Settings.Global.getString(mResolver, mSettingsKey)); @@ -173,6 +212,17 @@ public class BroadcastConstants { } } + private void updateDeviceConfigConstants(@NonNull DeviceConfig.Properties properties) { + MAX_RUNNING_PROCESS_QUEUES = properties.getInt("bcast_max_running_process_queues", + DEFAULT_MAX_RUNNING_PROCESS_QUEUES); + MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts", + DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); + DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis", + DEFAULT_DELAY_NORMAL_MILLIS); + DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis", + DEFAULT_DELAY_CACHED_MILLIS); + } + /** * Standard dumpsys support; invoked from BroadcastQueue dump */ diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index c18c65eec773..77eefb4e2743 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -21,12 +21,16 @@ import static com.android.server.am.BroadcastQueue.checkState; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; +import android.os.Trace; import android.os.UserHandle; import android.util.IndentingPrintWriter; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.Objects; /** * Queue of pending {@link BroadcastRecord} entries intended for delivery to a @@ -39,21 +43,13 @@ import java.util.ArrayDeque; * Internally each queue consists of a pending broadcasts which are waiting to * be dispatched, and a single active broadcast which is currently being * dispatched. + * <p> + * This entire class is marked as {@code NotThreadSafe} since it's the + * responsibility of the caller to always interact with a relevant lock held. */ -class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { - /** - * Default delay to apply to background broadcasts, giving a chance for - * debouncing of rapidly changing events. - */ - // TODO: shift hard-coded defaults to BroadcastConstants - private static final long DELAY_DEFAULT_MILLIS = 10_000; - - /** - * Default delay to apply to broadcasts targeting cached applications. - */ - // TODO: shift hard-coded defaults to BroadcastConstants - private static final long DELAY_CACHED_MILLIS = 30_000; - +// @NotThreadSafe +class BroadcastProcessQueue { + final @NonNull BroadcastConstants constants; final @NonNull String processName; final int uid; @@ -61,7 +57,14 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { * Linked list connection to another process under this {@link #uid} which * has a different {@link #processName}. */ - @Nullable BroadcastProcessQueue next; + @Nullable BroadcastProcessQueue processNameNext; + + /** + * Linked list connections to runnable process with lower and higher + * {@link #getRunnableAt()} times. + */ + @Nullable BroadcastProcessQueue runnableAtNext; + @Nullable BroadcastProcessQueue runnableAtPrev; /** * Currently known details about the target process; typically undefined @@ -70,6 +73,11 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { @Nullable ProcessRecord app; /** + * Track name to use for {@link Trace} events. + */ + @Nullable String traceTrackName; + + /** * Ordered collection of broadcasts that are waiting to be dispatched to * this process, as a pair of {@link BroadcastRecord} and the index into * {@link BroadcastRecord#receivers} that represents the receiver. @@ -87,6 +95,21 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { */ private int mActiveIndex; + /** + * Count of {@link #mActive} broadcasts that have been dispatched since this + * queue was last idle. + */ + private int mActiveCountSinceIdle; + + /** + * Flag indicating that the currently active broadcast is being dispatched + * was scheduled via a cold start. + */ + private boolean mActiveViaColdStart; + + /** + * Count of {@link #mPending} broadcasts of these various flavors. + */ private int mCountForeground; private int mCountOrdered; private int mCountAlarm; @@ -96,8 +119,10 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { private boolean mProcessCached; - public BroadcastProcessQueue(@NonNull String processName, int uid) { - this.processName = processName; + public BroadcastProcessQueue(@NonNull BroadcastConstants constants, + @NonNull String processName, int uid) { + this.constants = Objects.requireNonNull(constants); + this.processName = Objects.requireNonNull(processName); this.uid = uid; } @@ -131,6 +156,51 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { } /** + * Functional interface that tests a {@link BroadcastRecord} that has been + * previously enqueued in {@link BroadcastProcessQueue}. + */ + @FunctionalInterface + public interface BroadcastPredicate { + public boolean test(@NonNull BroadcastRecord r, int index); + } + + /** + * Functional interface that consumes a {@link BroadcastRecord} that has + * been previously enqueued in {@link BroadcastProcessQueue}. + */ + @FunctionalInterface + public interface BroadcastConsumer { + public void accept(@NonNull BroadcastRecord r, int index); + } + + /** + * Remove any broadcasts matching the given predicate. + * <p> + * Predicates that choose to remove a broadcast <em>must</em> finish + * delivery of the matched broadcast, to ensure that situations like ordered + * broadcasts are handled consistently. + */ + public boolean removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate, + @NonNull BroadcastConsumer consumer) { + boolean didSomething = false; + final Iterator<SomeArgs> it = mPending.iterator(); + while (it.hasNext()) { + final SomeArgs args = it.next(); + final BroadcastRecord record = (BroadcastRecord) args.arg1; + final int index = args.argi1; + if (predicate.test(record, index)) { + consumer.accept(record, index); + args.recycle(); + it.remove(); + didSomething = true; + } + } + // TODO: also check any active broadcast once we have a better "nonce" + // representing each scheduled broadcast to avoid races + return didSomething; + } + + /** * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @@ -163,6 +233,22 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { } /** + * Count of {@link #mActive} broadcasts that have been dispatched since this + * queue was last idle. + */ + public int getActiveCountSinceIdle() { + return mActiveCountSinceIdle; + } + + public void setActiveViaColdStart(boolean activeViaColdStart) { + mActiveViaColdStart = activeViaColdStart; + } + + public boolean getActiveViaColdStart() { + return mActiveViaColdStart; + } + + /** * Set the currently active broadcast to the next pending broadcast. */ public void makeActiveNextPending() { @@ -171,6 +257,8 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { final SomeArgs next = mPending.removeFirst(); mActive = (BroadcastRecord) next.arg1; mActiveIndex = next.argi1; + mActiveCountSinceIdle++; + mActiveViaColdStart = false; next.recycle(); if (mActive.isForeground()) { mCountForeground--; @@ -190,21 +278,56 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { public void makeActiveIdle() { mActive = null; mActiveIndex = 0; + mActiveCountSinceIdle = 0; + mActiveViaColdStart = false; } - public void setActiveDeliveryState(int deliveryState) { - checkState(isActive(), "isActive"); - mActive.setDeliveryState(mActiveIndex, deliveryState); + public void traceProcessStartingBegin() { + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTrackName, toShortString() + " starting", hashCode()); } + public void traceProcessRunningBegin() { + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTrackName, toShortString() + " running", hashCode()); + } + + public void traceProcessEnd() { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTrackName, hashCode()); + } + + public void traceActiveBegin() { + final int cookie = mActive.receivers.get(mActiveIndex).hashCode(); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTrackName, mActive.toShortString() + " scheduled", cookie); + } + + public void traceActiveEnd() { + final int cookie = mActive.receivers.get(mActiveIndex).hashCode(); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTrackName, cookie); + } + + /** + * Return the broadcast being actively dispatched in this process. + */ public @NonNull BroadcastRecord getActive() { checkState(isActive(), "isActive"); return mActive; } - public @NonNull Object getActiveReceiver() { + /** + * Return the index into {@link BroadcastRecord#receivers} of the receiver + * being actively dispatched in this process. + */ + public int getActiveIndex() { checkState(isActive(), "isActive"); - return mActive.receivers.get(mActiveIndex); + return mActiveIndex; + } + + public boolean isEmpty() { + return (mActive != null) && mPending.isEmpty(); } public boolean isActive() { @@ -230,7 +353,7 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { return mRunnableAt; } - private void invalidateRunnableAt() { + public void invalidateRunnableAt() { mRunnableAtInvalidated = true; } @@ -240,7 +363,17 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { private void updateRunnableAt() { final SomeArgs next = mPending.peekFirst(); if (next != null) { - final long runnableAt = ((BroadcastRecord) next.arg1).enqueueTime; + final BroadcastRecord r = (BroadcastRecord) next.arg1; + final int index = next.argi1; + + // If our next broadcast is ordered, and we're not the next receiver + // in line, then we're not runnable at all + if (r.ordered && r.finishedCount != index) { + mRunnableAt = Long.MAX_VALUE; + return; + } + + final long runnableAt = r.enqueueTime; if (mCountForeground > 0) { mRunnableAt = runnableAt; } else if (mCountOrdered > 0) { @@ -248,20 +381,73 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> { } else if (mCountAlarm > 0) { mRunnableAt = runnableAt; } else if (mProcessCached) { - mRunnableAt = runnableAt + DELAY_CACHED_MILLIS; + mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS; } else { - mRunnableAt = runnableAt + DELAY_DEFAULT_MILLIS; + mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS; } } else { mRunnableAt = Long.MAX_VALUE; } } - @Override - public int compareTo(BroadcastProcessQueue o) { - if (mRunnableAtInvalidated) updateRunnableAt(); - if (o.mRunnableAtInvalidated) o.updateRunnableAt(); - return Long.compare(mRunnableAt, o.mRunnableAt); + /** + * Insert the given queue into a sorted linked list of "runnable" queues. + * + * @param head the current linked list head + * @param item the queue to insert + * @return a potentially updated linked list head + */ + @VisibleForTesting + static @Nullable BroadcastProcessQueue insertIntoRunnableList( + @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) { + if (head == null) { + return item; + } + final long itemRunnableAt = item.getRunnableAt(); + BroadcastProcessQueue test = head; + BroadcastProcessQueue tail = null; + while (test != null) { + if (test.getRunnableAt() >= itemRunnableAt) { + item.runnableAtNext = test; + item.runnableAtPrev = test.runnableAtPrev; + if (item.runnableAtNext != null) { + item.runnableAtNext.runnableAtPrev = item; + } + if (item.runnableAtPrev != null) { + item.runnableAtPrev.runnableAtNext = item; + } + return (test == head) ? item : head; + } + tail = test; + test = test.runnableAtNext; + } + item.runnableAtPrev = tail; + item.runnableAtPrev.runnableAtNext = item; + return head; + } + + /** + * Remove the given queue from a sorted linked list of "runnable" queues. + * + * @param head the current linked list head + * @param item the queue to remove + * @return a potentially updated linked list head + */ + @VisibleForTesting + static @Nullable BroadcastProcessQueue removeFromRunnableList( + @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) { + if (head == item) { + head = item.runnableAtNext; + } + if (item.runnableAtNext != null) { + item.runnableAtNext.runnableAtPrev = item.runnableAtPrev; + } + if (item.runnableAtPrev != null) { + item.runnableAtPrev.runnableAtNext = item.runnableAtNext; + } + item.runnableAtNext = null; + item.runnableAtPrev = null; + return head; } @Override diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index e6c446aa40ad..972a1cea682f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -40,26 +40,20 @@ public abstract class BroadcastQueue { final @NonNull ActivityManagerService mService; final @NonNull Handler mHandler; - final @NonNull BroadcastConstants mConstants; final @NonNull BroadcastSkipPolicy mSkipPolicy; final @NonNull BroadcastHistory mHistory; final @NonNull String mQueueName; BroadcastQueue(@NonNull ActivityManagerService service, @NonNull Handler handler, - @NonNull String name, @NonNull BroadcastConstants constants, - @NonNull BroadcastSkipPolicy skipPolicy, @NonNull BroadcastHistory history) { + @NonNull String name, @NonNull BroadcastSkipPolicy skipPolicy, + @NonNull BroadcastHistory history) { mService = Objects.requireNonNull(service); mHandler = Objects.requireNonNull(handler); mQueueName = Objects.requireNonNull(name); - mConstants = Objects.requireNonNull(constants); mSkipPolicy = Objects.requireNonNull(skipPolicy); mHistory = Objects.requireNonNull(history); } - void start(@NonNull ContentResolver resolver) { - mConstants.startObserving(mHandler, resolver); - } - static void checkState(boolean state, String msg) { if (!state) { Slog.wtf(TAG, msg, new Throwable()); @@ -75,6 +69,8 @@ public abstract class BroadcastQueue { return mQueueName; } + public abstract void start(@NonNull ContentResolver resolver); + public abstract boolean isDelayBehindServices(); /** @@ -151,7 +147,7 @@ public abstract class BroadcastQueue { */ @GuardedBy("mService") public abstract boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName, - @Nullable Set<String> filterByClasses, int userId, boolean doit); + @Nullable Set<String> filterByClasses, int userId); /** * Quickly determine if this queue has broadcasts that are still waiting to diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 169f857995ce..28bd9c367fff 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -46,7 +46,6 @@ import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.usage.UsageEvents.Event; -import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.ContentResolver; import android.content.IIntentReceiver; @@ -97,6 +96,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { private static final String TAG_MU = TAG + POSTFIX_MU; private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST; + final BroadcastConstants mConstants; + /** * If true, we can delay broadcasts while waiting services to finish in the previous * receiver's process. @@ -193,14 +194,15 @@ public class BroadcastQueueImpl extends BroadcastQueue { BroadcastQueueImpl(ActivityManagerService service, Handler handler, String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy, BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) { - super(service, handler, name, constants, skipPolicy, history); + super(service, handler, name, skipPolicy, history); mHandler = new BroadcastHandler(handler.getLooper()); + mConstants = constants; mDelayBehindServices = allowDelayBehindServices; mSchedGroup = schedGroup; mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService); } - void start(ContentResolver resolver) { + public void start(ContentResolver resolver) { mDispatcher.start(); mConstants.startObserving(mHandler, resolver); } @@ -1444,7 +1446,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { return null; } - private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) { + static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) { // Only log after last receiver. // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the // last BroadcastRecord of the split broadcast which has non-null resultTo. @@ -1506,19 +1508,12 @@ public class BroadcastQueueImpl extends BroadcastQueue { if (targetPackage == null) { return; } - getUsageStatsManagerInternal().reportBroadcastDispatched( + mService.mUsageStatsService.reportBroadcastDispatched( r.callingUid, targetPackage, UserHandle.of(r.userId), r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(), mService.getUidStateLocked(targetUid)); } - @NonNull - private UsageStatsManagerInternal getUsageStatsManagerInternal() { - final UsageStatsManagerInternal usageStatsManagerInternal = - LocalServices.getService(UsageStatsManagerInternal.class); - return usageStatsManagerInternal; - } - private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) { if (r == null || proc == null || !r.allowBackgroundActivityStarts) { return; @@ -1657,7 +1652,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { // The ANR should only be triggered if we have a process record (app is non-null) if (!debugging && app != null) { - mService.mAnrHelper.appNotResponding(app, timeoutRecord); + mService.appNotResponding(app, timeoutRecord); } } finally { @@ -1690,18 +1685,15 @@ public class BroadcastQueueImpl extends BroadcastQueue { } public boolean cleanupDisabledPackageReceiversLocked( - String packageName, Set<String> filterByClasses, int userId, boolean doit) { + String packageName, Set<String> filterByClasses, int userId) { boolean didSomething = false; for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) { didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked( - packageName, filterByClasses, userId, doit); - if (!doit && didSomething) { - return true; - } + packageName, filterByClasses, userId, true); } didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName, - filterByClasses, userId, doit); + filterByClasses, userId, true); return didSomething; } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index b5e7b86e3129..a36a9f64bded 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -19,9 +19,21 @@ package com.android.server.am; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST; +import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; +import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList; +import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList; +import static com.android.server.am.BroadcastRecord.deliveryStateToString; +import static com.android.server.am.BroadcastRecord.getReceiverPackageName; import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; +import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; +import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER; import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; import android.annotation.NonNull; @@ -30,15 +42,23 @@ import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.UidObserver; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Handler; +import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; +import android.os.UserHandle; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; @@ -46,6 +66,13 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; +import com.android.internal.os.TimeoutRecord; +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer; +import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate; +import com.android.server.am.BroadcastRecord.DeliveryState; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -54,6 +81,7 @@ import java.util.ArrayList; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.function.Predicate; /** * Alternative {@link BroadcastQueue} implementation which pivots broadcasts to @@ -63,42 +91,54 @@ import java.util.concurrent.CountDownLatch; * {@link BroadcastProcessQueue} instance. Each queue has a concept of being * "runnable at" a particular time in the future, which supports arbitrarily * pausing or delaying delivery on a per-process basis. + * <p> + * To keep things easy to reason about, there is a <em>very strong</em> + * preference to have broadcast interactions flow through a consistent set of + * methods in this specific order: + * <ol> + * <li>{@link #updateRunnableList} promotes a per-process queue to be runnable + * when it has relevant pending broadcasts + * <li>{@link #updateRunningList} promotes a runnable queue to be running and + * schedules delivery of the first broadcast + * <li>{@link #scheduleReceiverColdLocked} requests any needed cold-starts, and + * results are reported back via {@link #onApplicationAttachedLocked} + * <li>{@link #scheduleReceiverWarmLocked} requests dispatch of the currently + * active broadcast to a running app, and results are reported back via + * {@link #finishReceiverLocked} + * </ol> */ class BroadcastQueueModernImpl extends BroadcastQueue { BroadcastQueueModernImpl(ActivityManagerService service, Handler handler, - BroadcastConstants constants) { - this(service, handler, constants, new BroadcastSkipPolicy(service), + BroadcastConstants fgConstants, BroadcastConstants bgConstants) { + this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service), new BroadcastHistory()); } BroadcastQueueModernImpl(ActivityManagerService service, Handler handler, - BroadcastConstants constants, BroadcastSkipPolicy skipPolicy, - BroadcastHistory history) { - super(service, handler, "modern", constants, skipPolicy, history); + BroadcastConstants fgConstants, BroadcastConstants bgConstants, + BroadcastSkipPolicy skipPolicy, BroadcastHistory history) { + super(service, handler, "modern", skipPolicy, history); + + // For the moment, read agnostic constants from foreground + mConstants = Objects.requireNonNull(fgConstants); + mFgConstants = Objects.requireNonNull(fgConstants); + mBgConstants = Objects.requireNonNull(bgConstants); + mLocalHandler = new Handler(handler.getLooper(), mLocalCallback); + + // We configure runnable size only once at boot; it'd be too complex to + // try resizing dynamically at runtime + mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES]; } - // TODO: add support for ordered broadcasts // TODO: add support for replacing pending broadcasts // TODO: add support for merging pending broadcasts - // TODO: add trace points for debugging broadcast flows - // TODO: record broadcast state change timing statistics - // TODO: record historical broadcast statistics + // TODO: consider reordering foreground broadcasts within queue - // TODO: pause queues for apps involved in backup/restore // TODO: pause queues when background services are running // TODO: pause queues when processes are frozen - // TODO: clean up queues for removed apps - - /** - * Maximum number of process queues to dispatch broadcasts to - * simultaneously. - */ - // TODO: shift hard-coded defaults to BroadcastConstants - private static final int MAX_RUNNING_PROCESS_QUEUES = 4; - /** * Map from UID to per-process broadcast queues. If a UID hosts more than * one process, each additional process is stored as a linked list using @@ -111,19 +151,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>(); /** - * Collection of queues which are "runnable". They're sorted by - * {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer + * Head of linked list containing queues which are "runnable". They're + * sorted by {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer * dispatching of longer-waiting broadcasts first. + * + * @see BroadcastProcessQueue#insertIntoRunnableList + * @see BroadcastProcessQueue#removeFromRunnableList */ - @GuardedBy("mService") - private final ArrayList<BroadcastProcessQueue> mRunnable = new ArrayList<>(); + private BroadcastProcessQueue mRunnableHead = null; /** - * Collection of queues which are "running". This will never be larger than - * {@link #MAX_RUNNING_PROCESS_QUEUES}. + * Array of queues which are currently "running", which may have gaps that + * are {@code null}. + * + * @see #getRunningSize + * @see #getRunningIndexOf */ @GuardedBy("mService") - private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>(); + private final BroadcastProcessQueue[] mRunning; /** * Single queue which is "running" but is awaiting a cold start to be @@ -139,7 +184,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @GuardedBy("mService") private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>(); + private final BroadcastConstants mConstants; + private final BroadcastConstants mFgConstants; + private final BroadcastConstants mBgConstants; + private static final int MSG_UPDATE_RUNNING_LIST = 1; + private static final int MSG_DELIVERY_TIMEOUT = 2; + private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); @@ -156,11 +207,51 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } return true; } + case MSG_DELIVERY_TIMEOUT: { + synchronized (mService) { + finishReceiverLocked((BroadcastProcessQueue) msg.obj, + BroadcastRecord.DELIVERY_TIMEOUT); + } + return true; + } + case MSG_BG_ACTIVITY_START_TIMEOUT: { + synchronized (mService) { + final SomeArgs args = (SomeArgs) msg.obj; + final ProcessRecord app = (ProcessRecord) args.arg1; + final BroadcastRecord r = (BroadcastRecord) args.arg2; + args.recycle(); + app.removeAllowBackgroundActivityStartsToken(r); + } + return true; + } } return false; }; /** + * Return the total number of active queues contained inside + * {@link #mRunning}. + */ + private int getRunningSize() { + int size = 0; + for (int i = 0; i < mRunning.length; i++) { + if (mRunning[i] != null) size++; + } + return size; + } + + /** + * Return the first index of the given value contained inside + * {@link #mRunning}, otherwise {@code -1}. + */ + private int getRunningIndexOf(@Nullable BroadcastProcessQueue test) { + for (int i = 0; i < mRunning.length; i++) { + if (mRunning[i] == test) return i; + } + return -1; + } + + /** * Consider updating the list of "runnable" queues, specifically with * relation to the given queue. * <p> @@ -170,32 +261,55 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @GuardedBy("mService") private void updateRunnableList(@NonNull BroadcastProcessQueue queue) { - if (mRunning.contains(queue)) { + if (getRunningIndexOf(queue) >= 0) { // Already running; they'll be reinserted into the runnable list // once they finish running, so no need to update them now return; } - // TODO: better optimize by using insertion sort data structure - mRunnable.remove(queue); - if (queue.isRunnable()) { - mRunnable.add(queue); + final boolean wantQueue = queue.isRunnable(); + final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null) + || (queue.runnableAtNext != null); + if (wantQueue) { + if (inQueue) { + // We're in a good state, but our position within the linked + // list might need to move based on a runnableAt change + final boolean prevLower = (queue.runnableAtPrev != null) + ? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true; + final boolean nextHigher = (queue.runnableAtNext != null) + ? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true; + if (!prevLower || !nextHigher) { + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + mRunnableHead = insertIntoRunnableList(mRunnableHead, queue); + } + } else { + mRunnableHead = insertIntoRunnableList(mRunnableHead, queue); + } + } else if (inQueue) { + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + } + + // If app isn't running, and there's nothing in the queue, clean up + if (queue.isEmpty() && !queue.isProcessWarm()) { + removeProcessQueue(queue.processName, queue.uid); } - mRunnable.sort(null); } /** * Consider updating the list of "running" queues. * <p> * This method can promote "runnable" queues to become "running", subject to - * a maximum of {@link #MAX_RUNNING_PROCESS_QUEUES} warm processes and only - * one pending cold-start. + * a maximum of {@link BroadcastConstants#MAX_RUNNING_PROCESS_QUEUES} warm + * processes and only one pending cold-start. */ @GuardedBy("mService") private void updateRunningList() { - int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size(); + int avail = mRunning.length - getRunningSize(); if (avail == 0) return; + final int cookie = traceBegin(TAG, "updateRunningList"); + final long now = SystemClock.uptimeMillis(); + // If someone is waiting to go idle, everything is runnable now final boolean waitingForIdle = !mWaitingForIdle.isEmpty(); @@ -204,9 +318,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); boolean updateOomAdj = false; - final long now = SystemClock.uptimeMillis(); - for (int i = 0; i < mRunnable.size() && avail > 0; i++) { - final BroadcastProcessQueue queue = mRunnable.get(i); + BroadcastProcessQueue queue = mRunnableHead; + while (queue != null && avail > 0) { + BroadcastProcessQueue nextQueue = queue.runnableAtNext; final long runnableAt = queue.getRunnableAt(); // If queues beyond this point aren't ready to run yet, schedule @@ -228,6 +342,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (mRunningColdStart == null) { mRunningColdStart = queue; } else { + // Move to considering next runnable queue + queue = nextQueue; continue; } } @@ -236,23 +352,39 @@ class BroadcastQueueModernImpl extends BroadcastQueue { + " from runnable to running; process is " + queue.app); // Allocate this available permit and start running! - mRunnable.remove(i); - mRunning.add(queue); + final int queueIndex = getRunningIndexOf(null); + mRunning[queueIndex] = queue; avail--; - i--; - queue.makeActiveNextPending(); + // Remove ourselves from linked list of runnable things + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + + // Emit all trace events for this process into a consistent track + queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]"; - // If we're already warm, schedule it; otherwise we'll wait for the - // cold start to circle back around + // If we're already warm, boost OOM adjust now; if cold we'll boost + // it after the app has been started if (processWarm) { + notifyStartedRunning(queue); + } + + // If we're already warm, schedule next pending broadcast now; + // otherwise we'll wait for the cold start to circle back around + queue.makeActiveNextPending(); + if (processWarm) { + queue.traceProcessRunningBegin(); scheduleReceiverWarmLocked(queue); } else { + queue.traceProcessStartingBegin(); scheduleReceiverColdLocked(queue); } - mService.enqueueOomAdjTargetLocked(queue.app); + // We've moved at least one process into running state above, so we + // need to kick off an OOM adjustment pass updateOomAdj = true; + + // Move to considering next runnable queue + queue = nextQueue; } if (updateOomAdj) { @@ -263,6 +395,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mWaitingForIdle.forEach((latch) -> latch.countDown()); mWaitingForIdle.clear(); } + + traceEnd(TAG, cookie); } @Override @@ -271,9 +405,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) { // We've been waiting for this app to cold start, and it's ready // now; dispatch its next broadcast and clear the slot - scheduleReceiverWarmLocked(mRunningColdStart); + final BroadcastProcessQueue queue = mRunningColdStart; mRunningColdStart = null; + queue.traceProcessEnd(); + queue.traceProcessRunningBegin(); + scheduleReceiverWarmLocked(queue); + // We might be willing to kick off another cold start enqueueUpdateRunningList(); didSomething = true; @@ -313,6 +451,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); didSomething = true; } + + // If queue has nothing else pending, consider cleaning it + if (queue.isEmpty()) { + updateRunnableList(queue); + } } return didSomething; @@ -321,7 +464,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) { final BroadcastProcessQueue queue = getProcessQueue(app); - if ((queue != null) && mRunning.contains(queue)) { + if ((queue != null) && getRunningIndexOf(queue) >= 0) { return queue.getPreferredSchedulingGroupLocked(); } return ProcessList.SCHED_GROUP_UNDEFINED; @@ -332,6 +475,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: handle empty receivers to deliver result immediately if (r.receivers == null) return; + final IntentFilter removeMatchingFilter = (r.options != null) + ? r.options.getRemoveMatchingFilter() : null; + if (removeMatchingFilter != null) { + final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate(); + skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> { + // We only allow caller to clear broadcasts they enqueued + return (testRecord.callingUid == r.callingUid) + && removeMatching.test(testRecord.intent); + }); + } + r.enqueueTime = SystemClock.uptimeMillis(); r.enqueueRealTime = SystemClock.elapsedRealtime(); r.enqueueClockTime = System.currentTimeMillis(); @@ -346,11 +500,20 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + /** + * Schedule the currently active broadcast on the given queue when we know + * the process is cold. This kicks off a cold start and will eventually call + * through to {@link #scheduleReceiverWarmLocked} once it's ready. + */ private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); + // Remember that active broadcast was scheduled via a cold start + queue.setActiveViaColdStart(true); + final BroadcastRecord r = queue.getActive(); - final Object receiver = queue.getActiveReceiver(); + final int index = queue.getActiveIndex(); + final Object receiver = r.receivers.get(index); final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName(); @@ -368,40 +531,86 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue); queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, false); - if (queue.app == null) { + if (queue.app != null) { + notifyStartedRunning(queue); + } else { mRunningColdStart = null; finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); } } + /** + * Schedule the currently active broadcast on the given queue when we know + * the process is warm. + * <p> + * There is a <em>very strong</em> preference to consistently handle all + * results by calling through to {@link #finishReceiverLocked}, both in the + * case where a broadcast is handled by a remote app, and the case where the + * broadcast was finished locally without the remote app being involved. + */ private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); final ProcessRecord app = queue.app; final BroadcastRecord r = queue.getActive(); - final Object receiver = queue.getActiveReceiver(); + final int index = queue.getActiveIndex(); + final Object receiver = r.receivers.get(index); - // TODO: schedule ANR timeout trigger event - // TODO: apply temp allowlist exemptions - // TODO: apply background activity launch exemptions + // If someone already finished this broadcast, finish immediately + final int oldDeliveryState = getDeliveryState(r, index); + if (isDeliveryStateTerminal(oldDeliveryState)) { + finishReceiverLocked(queue, oldDeliveryState); + return; + } + // Consider additional cases where we'd want fo finish immediately + if (app.isInFullBackup()) { + finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + return; + } if (mSkipPolicy.shouldSkip(r, receiver)) { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); return; } - final Intent receiverIntent = r.getReceiverIntent(receiver); if (receiverIntent == null) { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); return; } + if (!r.timeoutExempt) { + final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT; + mLocalHandler.sendMessageDelayed( + Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout); + } + + if (r.allowBackgroundActivityStarts) { + app.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken); + + final long timeout = r.isForeground() ? mFgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT + : mBgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT; + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = app; + args.arg2 = r; + mLocalHandler.sendMessageDelayed( + Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout); + } + + if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) { + mService.tempAllowlistUidLocked(queue.uid, + r.options.getTemporaryAppAllowlistDuration(), + r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(), + r.options.getTemporaryAppAllowlistType(), r.callingUid); + } + if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app); + setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED); + final IApplicationThread thread = app.getThread(); if (thread != null) { try { - queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED); if (receiver instanceof BroadcastFilter) { + notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); thread.scheduleRegisteredReceiver( ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, @@ -413,43 +622,91 @@ class BroadcastQueueModernImpl extends BroadcastQueue { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED); } } else { + notifyScheduleReceiver(app, r, (ResolveInfo) receiver); thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo, null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, app.mState.getReportedProcState()); } } catch (RemoteException e) { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); - synchronized (app.mService) { - app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null); - } + app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null); } } else { finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); } } + /** + * Schedule the final {@link BroadcastRecord#resultTo} delivery for an + * ordered broadcast; assumes the sender is still a warm process. + */ + private void scheduleResultTo(@NonNull BroadcastRecord r) { + if ((r.callerApp == null) || (r.resultTo == null)) return; + final ProcessRecord app = r.callerApp; + final IApplicationThread thread = app.getThread(); + if (thread != null) { + mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily( + app, OOM_ADJ_REASON_FINISH_RECEIVER); + try { + thread.scheduleRegisteredReceiver(r.resultTo, r.intent, + r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky, + r.userId, app.mState.getReportedProcState()); + } catch (RemoteException e) { + app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null); + } + } + } + @Override public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode, @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort, boolean waitForServices) { final BroadcastProcessQueue queue = getProcessQueue(app); + final BroadcastRecord r = queue.getActive(); + r.resultCode = resultCode; + r.resultData = resultData; + r.resultExtras = resultExtras; + if (!r.isNoAbort()) { + r.resultAbort = resultAbort; + } + + // When the caller aborted an ordered broadcast, we mark all remaining + // receivers as skipped + if (r.ordered && r.resultAbort) { + for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) { + setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED); + } + } + return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED); } - private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, int deliveryState) { + private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, + @DeliveryState int deliveryState) { checkState(queue.isActive(), "isActive"); - if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) { - Slog.w(TAG, "Failed delivery of " + queue.getActive() + " to " + queue); - } + final ProcessRecord app = queue.app; + final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); + final Object receiver = r.receivers.get(index); - queue.setActiveDeliveryState(deliveryState); + setDeliveryState(queue, r, index, receiver, deliveryState); - // TODO: cancel any outstanding ANR timeout - // TODO: limit number of broadcasts in a row to avoid starvation - // TODO: if we're the last receiver of this broadcast, record to history + if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) { + if (app != null && !app.isDebugging()) { + mService.appNotResponding(queue.app, TimeoutRecord + .forBroadcastReceiver("Broadcast of " + r.toShortString())); + } + } else { + mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue); + } - if (queue.isRunnable() && queue.isProcessWarm()) { + // Even if we have more broadcasts, if we've made reasonable progress + // and someone else is waiting, retire ourselves to avoid starvation + final boolean shouldRetire = (mRunnableHead != null) + && (queue.getActiveCountSinceIdle() > mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + + if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) { // We're on a roll; move onto the next broadcast for this process queue.makeActiveNextPending(); scheduleReceiverWarmLocked(queue); @@ -457,26 +714,156 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } else { // We've drained running broadcasts; maybe move back to runnable queue.makeActiveIdle(); - mRunning.remove(queue); - // App is no longer running a broadcast, so update its OOM - // adjust during our next pass; no need for an immediate update - mService.enqueueOomAdjTargetLocked(queue.app); + queue.traceProcessEnd(); + + final int queueIndex = getRunningIndexOf(queue); + mRunning[queueIndex] = null; updateRunnableList(queue); enqueueUpdateRunningList(); + + // Tell other OS components that app is not actively running, giving + // a chance to update OOM adjustment + notifyStoppedRunning(queue); return false; } } + /** + * Set the delivery state on the given broadcast, then apply any additional + * bookkeeping related to ordered broadcasts. + */ + private void setDeliveryState(@Nullable BroadcastProcessQueue queue, + @NonNull BroadcastRecord r, int index, @NonNull Object receiver, + @DeliveryState int newDeliveryState) { + final int oldDeliveryState = getDeliveryState(r, index); + + if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) { + Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from " + + deliveryStateToString(oldDeliveryState) + " to " + + deliveryStateToString(newDeliveryState)); + } + + r.setDeliveryState(index, newDeliveryState); + + // Emit any relevant tracing results when we're changing the delivery + // state as part of running from a queue + if (queue != null) { + if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) { + queue.traceActiveBegin(); + } else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) + && isDeliveryStateTerminal(newDeliveryState)) { + queue.traceActiveEnd(); + } + } + + // If we're moving into a terminal state, we might have internal + // bookkeeping to update for ordered broadcasts + if (!isDeliveryStateTerminal(oldDeliveryState) + && isDeliveryStateTerminal(newDeliveryState)) { + r.finishedCount++; + notifyFinishReceiver(queue, r, index, receiver); + + if (r.ordered) { + if (r.finishedCount < r.receivers.size()) { + // We just finished an ordered receiver, which means the + // next receiver might now be runnable + final Object nextReceiver = r.receivers.get(r.finishedCount); + final BroadcastProcessQueue nextQueue = getProcessQueue( + getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver)); + nextQueue.invalidateRunnableAt(); + updateRunnableList(nextQueue); + } else { + // Everything finished, so deliver final result + scheduleResultTo(r); + } + } + } + } + + private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) { + return r.getDeliveryState(index); + } + @Override - public boolean cleanupDisabledPackageReceiversLocked(String packageName, - Set<String> filterByClasses, int userId, boolean doit) { - // TODO: implement - return false; + public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName, + @Nullable Set<String> filterByClasses, int userId) { + final Predicate<BroadcastProcessQueue> queuePredicate; + final BroadcastPredicate broadcastPredicate; + if (packageName != null) { + // Caller provided a package and user ID, so we're focused on queues + // belonging to a specific UID + final int uid = mService.mPackageManagerInt.getPackageUid( + packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + queuePredicate = (q) -> { + return q.uid == uid; + }; + + // If caller provided a set of classes, filter to skip only those; + // otherwise we skip all broadcasts + if (filterByClasses != null) { + broadcastPredicate = (r, i) -> { + final Object receiver = r.receivers.get(i); + if (receiver instanceof ResolveInfo) { + final ActivityInfo info = ((ResolveInfo) receiver).activityInfo; + return packageName.equals(info.packageName) + && filterByClasses.contains(info.name); + } else { + return false; + } + }; + } else { + broadcastPredicate = (r, i) -> { + final Object receiver = r.receivers.get(i); + return packageName.equals(getReceiverPackageName(receiver)); + }; + } + } else { + // Caller is cleaning up an entire user ID; skip all broadcasts + queuePredicate = (q) -> { + return UserHandle.getUserId(q.uid) == userId; + }; + broadcastPredicate = BROADCAST_PREDICATE_ANY; + } + return skipMatchingBroadcasts(queuePredicate, broadcastPredicate); + } + + private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY = + (q) -> true; + private static final BroadcastPredicate BROADCAST_PREDICATE_ANY = + (r, i) -> true; + + /** + * Typical consumer that will skip the given broadcast, usually as a result + * of it matching a predicate. + */ + private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> { + setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED); + }; + + private boolean skipMatchingBroadcasts( + @NonNull Predicate<BroadcastProcessQueue> queuePredicate, + @NonNull BroadcastPredicate broadcastPredicate) { + // Note that we carefully preserve any "skipped" broadcasts in their + // queues so that we follow our normal flow for "finishing" a broadcast, + // which is where we handle things like ordered broadcasts. + boolean didSomething = false; + for (int i = 0; i < mProcessQueues.size(); i++) { + BroadcastProcessQueue leaf = mProcessQueues.valueAt(i); + while (leaf != null) { + if (queuePredicate.test(leaf)) { + didSomething |= leaf.removeMatchingBroadcasts(broadcastPredicate, + mBroadcastConsumerSkip); + } + leaf = leaf.processNameNext; + } + } + return didSomething; } @Override - void start(@NonNull ContentResolver resolver) { - super.start(resolver); + public void start(@NonNull ContentResolver resolver) { + mFgConstants.startObserving(mHandler, resolver); + mBgConstants.startObserving(mHandler, resolver); mService.registerUidObserver(new UidObserver() { @Override @@ -486,7 +873,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { while (leaf != null) { leaf.setProcessCached(cached); updateRunnableList(leaf); - leaf = leaf.next; + leaf = leaf.processNameNext; } enqueueUpdateRunningList(); } @@ -496,7 +883,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public boolean isIdleLocked() { - return mRunnable.isEmpty() && mRunning.isEmpty(); + return (mRunnableHead == null) && (getRunningSize() == 0); } @Override @@ -521,7 +908,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public String describeStateLocked() { - return mRunnable.size() + " runnable, " + mRunning.size() + " running"; + return getRunningSize() + " running"; } @Override @@ -535,50 +922,243 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: implement } + private int traceBegin(String trackName, String methodName) { + final int cookie = methodName.hashCode(); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + trackName, methodName, cookie); + return cookie; + } + + private void traceEnd(String trackName, int cookie) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + trackName, cookie); + } + private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid); } } - private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) { + /** + * Inform other parts of OS that the given broadcast queue has started + * running, typically for internal bookkeeping. + */ + private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) { + if (queue.app != null) { + queue.app.mReceivers.incrementCurReceivers(); + + queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); + + // Don't bump its LRU position if it's in the background restricted. + if (mService.mInternal.getRestrictionLevel( + queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) { + mService.updateLruProcessLocked(queue.app, false, null); + } + + mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app, + OOM_ADJ_REASON_START_RECEIVER); + + mService.enqueueOomAdjTargetLocked(queue.app); + } + } + + /** + * Inform other parts of OS that the given broadcast queue has stopped + * running, typically for internal bookkeeping. + */ + private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) { + if (queue.app != null) { + // Update during our next pass; no need for an immediate update + mService.enqueueOomAdjTargetLocked(queue.app); + + queue.app.mReceivers.decrementCurReceivers(); + } + } + + /** + * Inform other parts of OS that the given broadcast was just scheduled for + * a registered receiver, typically for internal bookkeeping. + */ + private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app, + @NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) { + reportUsageStatsBroadcastDispatched(app, r); + } + + /** + * Inform other parts of OS that the given broadcast was just scheduled for + * a manifest receiver, typically for internal bookkeeping. + */ + private void notifyScheduleReceiver(@NonNull ProcessRecord app, + @NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) { + reportUsageStatsBroadcastDispatched(app, r); + + final String receiverPackageName = receiver.activityInfo.packageName; + app.addPackage(receiverPackageName, + receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats); + + final boolean targetedBroadcast = r.intent.getComponent() != null; + final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName); + if (targetedBroadcast && !targetedSelf) { + mService.mUsageStatsService.reportEvent(receiverPackageName, + r.userId, Event.APP_COMPONENT_USED); + } + + mService.notifyPackageUse(receiverPackageName, + PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); + + mService.mPackageManagerInt.setPackageStoppedState( + receiverPackageName, false, r.userId); + } + + private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app, + @NonNull BroadcastRecord r) { + final long idForResponseEvent = (r.options != null) + ? r.options.getIdForResponseEvent() : 0L; + if (idForResponseEvent <= 0) return; + + final String targetPackage; + if (r.intent.getPackage() != null) { + targetPackage = r.intent.getPackage(); + } else if (r.intent.getComponent() != null) { + targetPackage = r.intent.getComponent().getPackageName(); + } else { + targetPackage = null; + } + if (targetPackage == null) return; + + mService.mUsageStatsService.reportBroadcastDispatched(r.callingUid, targetPackage, + UserHandle.of(r.userId), idForResponseEvent, SystemClock.elapsedRealtime(), + mService.getUidStateLocked(app.uid)); + } + + /** + * Inform other parts of OS that the given broadcast was just finished, + * typically for internal bookkeeping. + */ + private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue, + @NonNull BroadcastRecord r, int index, @NonNull Object receiver) { + // Report statistics for each individual receiver + final int uid = getReceiverUid(receiver); + final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid; + final String actionName = ActivityManagerService.getShortAction(r.intent.getAction()); + final int receiverType = (receiver instanceof BroadcastFilter) + ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME + : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST; + final int type; + if (queue == null) { + type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN; + } else if (queue.getActiveViaColdStart()) { + type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD; + } else { + type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; + } + // With the new per-process queues, there's no delay between being + // "dispatched" and "scheduled", so we report no "receive delay" + final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime; + final long receiveDelay = 0; + final long finishDelay = r.duration[index]; + FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName, + receiverType, type, dispatchDelay, receiveDelay, finishDelay); + + final boolean recordFinished = (r.finishedCount == r.receivers.size()); + if (recordFinished) { + mHistory.addBroadcastToHistoryLocked(r); + + r.nextReceiver = r.receivers.size(); + BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r); + + if (r.intent.getComponent() == null && r.intent.getPackage() == null + && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { + int manifestCount = 0; + int manifestSkipCount = 0; + for (int i = 0; i < r.receivers.size(); i++) { + if (r.receivers.get(i) instanceof ResolveInfo) { + manifestCount++; + if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) { + manifestSkipCount++; + } + } + } + + final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime; + mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, + manifestCount, manifestSkipCount, dispatchTime); + } + } + } + + @VisibleForTesting + @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) { return getOrCreateProcessQueue(app.processName, app.info.uid); } - private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName, + @VisibleForTesting + @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName, int uid) { BroadcastProcessQueue leaf = mProcessQueues.get(uid); while (leaf != null) { if (Objects.equals(leaf.processName, processName)) { return leaf; - } else if (leaf.next == null) { + } else if (leaf.processNameNext == null) { break; } - leaf = leaf.next; + leaf = leaf.processNameNext; } - BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid); + BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid); created.app = mService.getProcessRecordLocked(processName, uid); if (leaf == null) { mProcessQueues.put(uid, created); } else { - leaf.next = created; + leaf.processNameNext = created; } return created; } - private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) { + @VisibleForTesting + @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) { return getProcessQueue(app.processName, app.info.uid); } - private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) { + @VisibleForTesting + @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) { + BroadcastProcessQueue leaf = mProcessQueues.get(uid); + while (leaf != null) { + if (Objects.equals(leaf.processName, processName)) { + return leaf; + } + leaf = leaf.processNameNext; + } + return null; + } + + @VisibleForTesting + @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) { + return removeProcessQueue(app.processName, app.info.uid); + } + + @VisibleForTesting + @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName, + int uid) { + BroadcastProcessQueue prev = null; BroadcastProcessQueue leaf = mProcessQueues.get(uid); while (leaf != null) { if (Objects.equals(leaf.processName, processName)) { + if (prev != null) { + prev.processNameNext = leaf.processNameNext; + } else { + if (leaf.processNameNext != null) { + mProcessQueues.put(uid, leaf.processNameNext); + } else { + mProcessQueues.remove(uid); + } + } return leaf; } - leaf = leaf.next; + prev = leaf; + leaf = leaf.processNameNext; } return null; } @@ -606,7 +1186,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { BroadcastProcessQueue leaf = mProcessQueues.valueAt(i); while (leaf != null) { leaf.dumpLocked(ipw); - leaf = leaf.next; + leaf = leaf.processNameNext; } } ipw.decreaseIndent(); @@ -614,13 +1194,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.println(); ipw.println("🧍 Runnable:"); ipw.increaseIndent(); - if (mRunnable.isEmpty()) { + if (mRunnableHead == null) { ipw.println("(none)"); } else { - for (BroadcastProcessQueue queue : mRunnable) { + BroadcastProcessQueue queue = mRunnableHead; + while (queue != null) { TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw); ipw.print(' '); ipw.println(queue.toShortString()); + queue = queue.runnableAtNext; } } ipw.decreaseIndent(); @@ -628,16 +1210,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.println(); ipw.println("🏃 Running:"); ipw.increaseIndent(); - if (mRunning.isEmpty()) { - ipw.println("(none)"); - } else { - for (BroadcastProcessQueue queue : mRunning) { - if (queue == mRunningColdStart) { - ipw.print("🥶 "); - } else { - ipw.print("\u3000 "); - } + for (BroadcastProcessQueue queue : mRunning) { + if ((queue != null) && (queue == mRunningColdStart)) { + ipw.print("🥶 "); + } else { + ipw.print("\u3000 "); + } + if (queue != null) { ipw.println(queue.toShortString()); + } else { + ipw.println("(none)"); } } ipw.decreaseIndent(); diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 96fd362d256c..ae7f2a5d8565 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -23,7 +23,10 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import static com.android.server.am.BroadcastQueue.checkState; +import android.annotation.DurationMillisLong; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManagerInternal; @@ -48,6 +51,8 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -83,7 +88,7 @@ final class BroadcastRecord extends Binder { final int appOp; // an app op that is associated with this broadcast final BroadcastOptions options; // BroadcastOptions supplied by caller final List receivers; // contains BroadcastFilter and ResolveInfo - final int[] delivery; // delivery state of each receiver + final @DeliveryState int[] delivery; // delivery state of each receiver final long[] scheduledTime; // uptimeMillis when each receiver was scheduled final long[] duration; // duration a receiver took to process broadcast IIntentReceiver resultTo; // who receives final result if non-null @@ -108,6 +113,7 @@ final class BroadcastRecord extends Binder { int anrCount; // has this broadcast record hit any ANRs? int manifestCount; // number of manifest receivers dispatched. int manifestSkipCount; // number of manifest receivers skipped. + int finishedCount; // number of receivers finished. BroadcastQueue queue; // the outbound queue handling this broadcast // if set to true, app's process will be temporarily allowed to start activities from background @@ -141,6 +147,45 @@ final class BroadcastRecord extends Binder { /** Terminal state: failure to dispatch */ static final int DELIVERY_FAILURE = 5; + @IntDef(flag = false, prefix = { "DELIVERY_" }, value = { + DELIVERY_PENDING, + DELIVERY_DELIVERED, + DELIVERY_SKIPPED, + DELIVERY_TIMEOUT, + DELIVERY_SCHEDULED, + DELIVERY_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeliveryState {} + + static @NonNull String deliveryStateToString(@DeliveryState int deliveryState) { + switch (deliveryState) { + case DELIVERY_PENDING: return "Pending"; + case DELIVERY_DELIVERED: return "Delivered"; + case DELIVERY_SKIPPED: return "Skipped"; + case DELIVERY_TIMEOUT: return "Timeout"; + case DELIVERY_SCHEDULED: return "Scheduled"; + case DELIVERY_FAILURE: return "Failure"; + default: return Integer.toString(deliveryState); + } + } + + /** + * Return if the given delivery state is "terminal", where no additional + * delivery state changes will be made. + */ + static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) { + switch (deliveryState) { + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + return true; + default: + return false; + } + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. @@ -257,15 +302,7 @@ final class BroadcastRecord extends Binder { for (int i = 0; i < N; i++) { Object o = receivers.get(i); pw.print(prefix); - switch (delivery[i]) { - case DELIVERY_PENDING: pw.print("Pending"); break; - case DELIVERY_DELIVERED: pw.print("Deliver"); break; - case DELIVERY_SKIPPED: pw.print("Skipped"); break; - case DELIVERY_TIMEOUT: pw.print("Timeout"); break; - case DELIVERY_SCHEDULED: pw.print("Schedul"); break; - case DELIVERY_FAILURE: pw.print("Failure"); break; - default: pw.print("???????"); break; - } + pw.print(deliveryStateToString(delivery[i])); pw.print(" "); TimeUtils.formatDuration(duration[i], pw); pw.print(" #"); pw.print(i); pw.print(": "); if (o instanceof BroadcastFilter) { @@ -514,7 +551,7 @@ final class BroadcastRecord extends Binder { * Update the delivery state of the given {@link #receivers} index. * Automatically updates any time measurements related to state changes. */ - void setDeliveryState(int index, int deliveryState) { + void setDeliveryState(int index, @DeliveryState int deliveryState) { delivery[index] = deliveryState; switch (deliveryState) { @@ -527,6 +564,10 @@ final class BroadcastRecord extends Binder { } } + @DeliveryState int getDeliveryState(int index) { + return delivery[index]; + } + boolean isForeground() { return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; } @@ -535,6 +576,10 @@ final class BroadcastRecord extends Binder { return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0; } + boolean isNoAbort() { + return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0; + } + @NonNull String getHostingRecordTriggerType() { if (alarm) { return HostingRecord.TRIGGER_TYPE_ALARM; @@ -588,7 +633,7 @@ final class BroadcastRecord extends Binder { } } - static String getReceiverProcessName(@NonNull Object receiver) { + static @NonNull String getReceiverProcessName(@NonNull Object receiver) { if (receiver instanceof BroadcastFilter) { return ((BroadcastFilter) receiver).receiverList.app.processName; } else /* if (receiver instanceof ResolveInfo) */ { @@ -596,6 +641,14 @@ final class BroadcastRecord extends Binder { } } + static @NonNull String getReceiverPackageName(@NonNull Object receiver) { + if (receiver instanceof BroadcastFilter) { + return ((BroadcastFilter) receiver).receiverList.app.info.packageName; + } else /* if (receiver instanceof ResolveInfo) */ { + return ((ResolveInfo) receiver).activityInfo.packageName; + } + } + public BroadcastRecord maybeStripForHistory() { if (!intent.canStripForHistory()) { return this; @@ -647,13 +700,21 @@ final class BroadcastRecord extends Binder { @Override public String toString() { + String label = intent.getAction(); + if (label == null) { + label = intent.toString(); + } return "BroadcastRecord{" + Integer.toHexString(System.identityHashCode(this)) - + " u" + userId + " " + intent.getAction() + "}"; + + " u" + userId + " " + label + "}"; } public String toShortString() { - return intent.getAction() + "/u" + userId; + String label = intent.getAction(); + if (label == null) { + label = intent.toString(); + } + return label + "/u" + userId; } public void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/services/core/java/com/android/server/am/ProcessReceiverRecord.java b/services/core/java/com/android/server/am/ProcessReceiverRecord.java index 8d3e9669ea00..34a2b03f1d7d 100644 --- a/services/core/java/com/android/server/am/ProcessReceiverRecord.java +++ b/services/core/java/com/android/server/am/ProcessReceiverRecord.java @@ -34,29 +34,61 @@ final class ProcessReceiverRecord { */ private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>(); + private int mCurReceiversSize; + /** * All IIntentReceivers that are registered from this process. */ private final ArraySet<ReceiverList> mReceivers = new ArraySet<>(); int numberOfCurReceivers() { - return mCurReceivers.size(); + return mCurReceiversSize; + } + + void incrementCurReceivers() { + mCurReceiversSize++; } + void decrementCurReceivers() { + mCurReceiversSize--; + } + + /** + * @deprecated we're moving towards tracking only a reference count to + * improve performance. + */ + @Deprecated BroadcastRecord getCurReceiverAt(int index) { return mCurReceivers.valueAt(index); } + /** + * @deprecated we're moving towards tracking only a reference count to + * improve performance. + */ + @Deprecated boolean hasCurReceiver(BroadcastRecord receiver) { return mCurReceivers.contains(receiver); } + /** + * @deprecated we're moving towards tracking only a reference count to + * improve performance. + */ + @Deprecated void addCurReceiver(BroadcastRecord receiver) { mCurReceivers.add(receiver); + mCurReceiversSize = mCurReceivers.size(); } + /** + * @deprecated we're moving towards tracking only a reference count to + * improve performance. + */ + @Deprecated void removeCurReceiver(BroadcastRecord receiver) { mCurReceivers.remove(receiver); + mCurReceiversSize = mCurReceivers.size(); } int numberOfReceivers() { diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index f8558e80588c..9ccf74115ba0 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -60,6 +60,14 @@ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } ] + }, + { + "file_patterns": ["Broadcast"], + "name": "FrameworksMockingServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BroadcastQueueTest" }, + { "include-filter": "com.android.server.am.BroadcastQueueModernImplTest" } + ] } ], "presubmit-large": [ diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 6775c9952ddd..7c7b01c96509 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1641,7 +1641,7 @@ class UserController implements Handler.Callback { } mInjector.updateUserConfiguration(); updateCurrentProfileIds(); - mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds()); + mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device // with the option to show the user switcher on the keyguard. @@ -1655,7 +1655,6 @@ class UserController implements Handler.Callback { } else { final Integer currentUserIdInt = mCurrentUserId; updateCurrentProfileIds(); - mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds()); synchronized (mLock) { mUserLru.remove(currentUserIdInt); mUserLru.add(currentUserIdInt); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index af73c2bac6f9..b844fc6b43ab 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -33,7 +33,6 @@ import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; @@ -45,7 +44,6 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; -import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; import static android.app.AppOpsManager.OpEventProxyInfo; import static android.app.AppOpsManager.RestrictionBypass; @@ -57,7 +55,6 @@ import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTIO import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; -import static android.app.AppOpsManager.makeKey; import static android.app.AppOpsManager.modeToName; import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; @@ -71,7 +68,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; import android.Manifest; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -131,10 +127,7 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; -import android.util.LongSparseArray; import android.util.Pair; -import android.util.Pools; -import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -198,7 +191,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.Scanner; import java.util.Set; @@ -241,14 +233,22 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private final @Nullable File mNoteOpCallerStacktracesFile; final Handler mHandler; - /** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */ + /** + * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new + * objects + */ @GuardedBy("this") - private final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool(); + final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = + new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); - /** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */ + /** + * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate + * new objects + */ @GuardedBy("this") - private final InProgressStartOpEventPool mInProgressStartOpEventPool = - new InProgressStartOpEventPool(); + final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = + new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, + MAX_UNUSED_POOLED_OBJECTS); private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); @@ -383,60 +383,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } /** - * An unsynchronized pool of {@link OpEventProxyInfo} objects. - */ - private class OpEventProxyInfoPool extends SimplePool<OpEventProxyInfo> { - OpEventProxyInfoPool() { - super(MAX_UNUSED_POOLED_OBJECTS); - } - - OpEventProxyInfo acquire(@IntRange(from = 0) int uid, @Nullable String packageName, - @Nullable String attributionTag) { - OpEventProxyInfo recycled = acquire(); - if (recycled != null) { - recycled.reinit(uid, packageName, attributionTag); - return recycled; - } - - return new OpEventProxyInfo(uid, packageName, attributionTag); - } - } - - /** - * An unsynchronized pool of {@link InProgressStartOpEvent} objects. - */ - private class InProgressStartOpEventPool extends SimplePool<InProgressStartOpEvent> { - InProgressStartOpEventPool() { - super(MAX_UNUSED_POOLED_OBJECTS); - } - - InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, - @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - - InProgressStartOpEvent recycled = acquire(); - - OpEventProxyInfo proxyInfo = null; - if (proxyUid != Process.INVALID_UID) { - proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); - } - - if (recycled != null) { - recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath, - uidState, flags, proxyInfo, attributionFlags, attributionChainId, - mOpEventProxyInfoPool); - return recycled; - } - - return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag, - onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId); - } - } - - /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while * holding the AppOpsService lock. @@ -652,740 +598,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** A in progress startOp->finishOp event */ - private static final class InProgressStartOpEvent implements IBinder.DeathRecipient { - /** Wall clock time of startOp event (not monotonic) */ - private long mStartTime; - - /** Elapsed time since boot of startOp event */ - private long mStartElapsedTime; - - /** Id of the client that started the event */ - private @NonNull IBinder mClientId; - - /** The attribution tag for this operation */ - private @Nullable String mAttributionTag; - - /** To call when client dies */ - private @NonNull Runnable mOnDeath; - - /** uidstate used when calling startOp */ - private @AppOpsManager.UidState int mUidState; - - /** Proxy information of the startOp event */ - private @Nullable OpEventProxyInfo mProxy; - - /** Proxy flag information */ - private @OpFlags int mFlags; - - /** How many times the op was started but not finished yet */ - int numUnfinishedStarts; - - /** The attribution flags related to this event */ - private @AttributionFlags int mAttributionFlags; - - /** The id of the attribution chain this even is a part of */ - private int mAttributionChainId; - - /** - * Create a new {@link InProgressStartOpEvent}. - * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} - * @param attributionTag The attribution tag for the operation. - * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for - * @param attributionFlags the attribution flags for this operation. - * @param attributionChainId the unique id of the attribution chain this op is a part of. - * @param proxy The proxy information, if {@link #startProxyOperation} was called - * @param flags The trusted/nontrusted/self flags. - * - * @throws RemoteException If the client is dying - */ - private InProgressStartOpEvent(long startTime, long startElapsedTime, - @NonNull IBinder clientId, @Nullable String attributionTag, - @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, - @Nullable OpEventProxyInfo proxy, @OpFlags int flags, - @AttributionFlags int attributionFlags, int attributionChainId) - throws RemoteException { - mStartTime = startTime; - mStartElapsedTime = startElapsedTime; - mClientId = clientId; - mAttributionTag = attributionTag; - mOnDeath = onDeath; - mUidState = uidState; - mProxy = proxy; - mFlags = flags; - mAttributionFlags = attributionFlags; - mAttributionChainId = attributionChainId; - - clientId.linkToDeath(this, 0); - } - - /** Clean up event */ - public void finish() { - try { - mClientId.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - // Either not linked, or already unlinked. Either way, nothing to do. - } - } - - @Override - public void binderDied() { - mOnDeath.run(); - } - - /** - * Reinit existing object with new state. - * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} - * @param attributionTag The attribution tag for this operation. - * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for - * @param flags The flags relating to the proxy - * @param proxy The proxy information, if {@link #startProxyOperation} was called - * @param attributionFlags the attribution flags for this operation. - * @param attributionChainId the unique id of the attribution chain this op is a part of. - * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to - * - * @throws RemoteException If the client is dying - */ - public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, - @Nullable String attributionTag, @NonNull Runnable onDeath, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags, - int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool - ) throws RemoteException { - mStartTime = startTime; - mStartElapsedTime = startElapsedTime; - mClientId = clientId; - mAttributionTag = attributionTag; - mOnDeath = onDeath; - mUidState = uidState; - mFlags = flags; - - if (mProxy != null) { - proxyPool.release(mProxy); - } - mProxy = proxy; - mAttributionFlags = attributionFlags; - mAttributionChainId = attributionChainId; - - clientId.linkToDeath(this, 0); - } - - /** @return Wall clock time of startOp event */ - public long getStartTime() { - return mStartTime; - } - - /** @return Elapsed time since boot of startOp event */ - public long getStartElapsedTime() { - return mStartElapsedTime; - } - - /** @return Id of the client that started the event */ - public @NonNull IBinder getClientId() { - return mClientId; - } - - /** @return uidstate used when calling startOp */ - public @AppOpsManager.UidState int getUidState() { - return mUidState; - } - - /** @return proxy tag for the access */ - public @Nullable OpEventProxyInfo getProxy() { - return mProxy; - } - - /** @return flags used for the access */ - public @OpFlags int getFlags() { - return mFlags; - } - - /** @return attributoin flags used for the access */ - public @AttributionFlags int getAttributionFlags() { - return mAttributionFlags; - } - - /** @return attribution chain id for the access */ - public int getAttributionChainId() { - return mAttributionChainId; - } - } - - private final class AttributedOp { - public final @Nullable String tag; - public final @NonNull Op parent; - - /** - * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination - * - * <p>Key is {@link AppOpsManager#makeKey} - */ - @GuardedBy("AppOpsService.this") - private @Nullable LongSparseArray<NoteOpEvent> mAccessEvents; - - /** - * Last rejected accesses for each uidState/opFlag combination - * - * <p>Key is {@link AppOpsManager#makeKey} - */ - @GuardedBy("AppOpsService.this") - private @Nullable LongSparseArray<NoteOpEvent> mRejectEvents; - - /** - * Currently in progress startOp events - * - * <p>Key is clientId - */ - @GuardedBy("AppOpsService.this") - private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents; - - /** - * Currently paused startOp events - * - * <p>Key is clientId - */ - @GuardedBy("AppOpsService.this") - private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents; - - AttributedOp(@Nullable String tag, @NonNull Op parent) { - this.tag = tag; - this.parent = parent; - } - - /** - * Update state when noteOp was rejected or startOp->finishOp event finished - * - * @param proxyUid The uid of the proxy - * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void accessed(int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @OpFlags int flags) { - long accessTime = System.currentTimeMillis(); - accessed(accessTime, -1, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags); - - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, - AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); - } - - /** - * Add an access that was previously collected. - * - * @param noteTime The time of the event - * @param duration The duration of the event - * @param proxyUid The uid of the proxy - * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void accessed(long noteTime, long duration, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags) { - long key = makeKey(uidState, flags); - - if (mAccessEvents == null) { - mAccessEvents = new LongSparseArray<>(1); - } - - OpEventProxyInfo proxyInfo = null; - if (proxyUid != Process.INVALID_UID) { - proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); - } - - NoteOpEvent existingEvent = mAccessEvents.get(key); - if (existingEvent != null) { - existingEvent.reinit(noteTime, duration, proxyInfo, mOpEventProxyInfoPool); - } else { - mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo)); - } - } - - /** - * Update state when noteOp/startOp was rejected. - * - * @param uidState UID state of the app noteOp is called for - * @param flags OpFlags of the call - */ - public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) { - rejected(System.currentTimeMillis(), uidState, flags); - - mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName, - tag, uidState, flags); - } - - /** - * Add an rejection that was previously collected - * - * @param noteTime The time of the event - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void rejected(long noteTime, @AppOpsManager.UidState int uidState, - @OpFlags int flags) { - long key = makeKey(uidState, flags); - - if (mRejectEvents == null) { - mRejectEvents = new LongSparseArray<>(1); - } - - // We do not collect proxy information for rejections yet - NoteOpEvent existingEvent = mRejectEvents.get(key); - if (existingEvent != null) { - existingEvent.reinit(noteTime, -1, null, mOpEventProxyInfoPool); - } else { - mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null)); - } - } - - /** - * Update state when start was called - * - * @param clientId Id of the startOp caller - * @param proxyUid The UID of the proxy app - * @param proxyPackageName The package name of the proxy app - * @param proxyAttributionTag The attribution tag of the proxy app - * @param uidState UID state of the app startOp is called for - * @param flags The proxy flags - * @param attributionFlags The attribution flags associated with this operation. - * @param attributionChainId The if of the attribution chain this operations is a part of. - */ - public void started(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags, - attributionChainId); - } - - private void started(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags, - int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded, - /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId); - } - - private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, true, attributionFlags, attributionChainId); - } - - if (isStarted && mInProgressEvents == null) { - mInProgressEvents = new ArrayMap<>(1); - } else if (!isStarted && mPausedInProgressEvents == null) { - mPausedInProgressEvents = new ArrayMap<>(1); - } - ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted - ? mInProgressEvents : mPausedInProgressEvents; - - long startTime = System.currentTimeMillis(); - InProgressStartOpEvent event = events.get(clientId); - if (event == null) { - event = mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, tag, - PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, - attributionFlags, attributionChainId); - events.put(clientId, event); - } else { - if (uidState != event.mUidState) { - onUidStateChanged(uidState); - } - } - - event.numUnfinishedStarts++; - - if (isStarted) { - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime, attributionFlags, - attributionChainId); - } - } - - /** - * Update state when finishOp was called. Will finish started ops, and delete paused ops. - * - * @param clientId Id of the finishOp caller - */ - public void finished(@NonNull IBinder clientId) { - finished(clientId, true); - } - - private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) { - finishOrPause(clientId, triggerCallbackIfNeeded, false); - } - - /** - * Update state when paused or finished is called. If pausing, it records the op as - * stopping in the HistoricalRegistry, but does not delete it. - */ - private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded, - boolean isPausing) { - int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1; - if (indexOfToken < 0) { - finishPossiblyPaused(clientId, isPausing); - return; - } - - InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); - if (!isPausing) { - event.numUnfinishedStarts--; - } - // If we are pausing, create a NoteOpEvent, but don't change the InProgress event - if (event.numUnfinishedStarts == 0 || isPausing) { - if (!isPausing) { - event.finish(); - mInProgressEvents.removeAt(indexOfToken); - } - - if (mAccessEvents == null) { - mAccessEvents = new LongSparseArray<>(1); - } - - OpEventProxyInfo proxyCopy = event.getProxy() != null - ? new OpEventProxyInfo(event.getProxy()) : null; - - long accessDurationMillis = - SystemClock.elapsedRealtime() - event.getStartElapsedTime(); - NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(), - accessDurationMillis, proxyCopy); - mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), - finishedEvent); - - mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), - event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), - event.getAttributionFlags(), event.getAttributionChainId()); - - if (!isPausing) { - mInProgressStartOpEventPool.release(event); - if (mInProgressEvents.isEmpty()) { - mInProgressEvents = null; - - // TODO ntmyren: Also callback for single attribution tag activity changes - if (triggerCallbackIfNeeded && !parent.isRunning()) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, false, event.getAttributionFlags(), - event.getAttributionChainId()); - } - } - } - } - } - - // Finish or pause (no-op) an already paused op - private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { - if (!isPaused()) { - Slog.wtf(TAG, "No ops running or paused"); - return; - } - - int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); - if (indexOfToken < 0) { - Slog.wtf(TAG, "No op running or paused for the client"); - return; - } else if (isPausing) { - // already paused - return; - } - - // no need to record a paused event finishing. - InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); - event.numUnfinishedStarts--; - if (event.numUnfinishedStarts == 0) { - mPausedInProgressEvents.removeAt(indexOfToken); - mInProgressStartOpEventPool.release(event); - if (mPausedInProgressEvents.isEmpty()) { - mPausedInProgressEvents = null; - } - } - } - - /** - * Create an event that will be started, if the op is unpaused. - */ - public void createPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags, true, false, attributionFlags, attributionChainId); - } - - /** - * Pause all currently started ops. This will create a HistoricalRegistry - */ - public void pause() { - if (!isRunning()) { - return; - } - - if (mPausedInProgressEvents == null) { - mPausedInProgressEvents = new ArrayMap<>(1); - } - - for (int i = 0; i < mInProgressEvents.size(); i++) { - InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - mPausedInProgressEvents.put(event.mClientId, event); - finishOrPause(event.mClientId, true, true); - - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, false, - event.getAttributionFlags(), event.getAttributionChainId()); - } - mInProgressEvents = null; - } - - /** - * Unpause all currently paused ops. This will reinitialize their start and duration - * times, but keep all other values the same - */ - public void resume() { - if (!isPaused()) { - return; - } - - if (mInProgressEvents == null) { - mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size()); - } - boolean shouldSendActive = !mPausedInProgressEvents.isEmpty() - && mInProgressEvents.isEmpty(); - - long startTime = System.currentTimeMillis(); - for (int i = 0; i < mPausedInProgressEvents.size(); i++) { - InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); - mInProgressEvents.put(event.mClientId, event); - event.mStartElapsedTime = SystemClock.elapsedRealtime(); - event.mStartTime = startTime; - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.mUidState, event.mFlags, startTime, - event.getAttributionFlags(), event.getAttributionChainId()); - if (shouldSendActive) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, true, event.getAttributionFlags(), event.getAttributionChainId()); - } - // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND - // TODO ntmyren: figure out how to get the real mode. - scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, - event.getAttributionFlags(), event.getAttributionChainId()); - } - mPausedInProgressEvents = null; - } - - /** - * Called in the case the client dies without calling finish first - * - * @param clientId The client that died - */ - void onClientDeath(@NonNull IBinder clientId) { - synchronized (AppOpsService.this) { - if (!isPaused() && !isRunning()) { - return; - } - - ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused() - ? mPausedInProgressEvents : mInProgressEvents; - InProgressStartOpEvent deadEvent = events.get(clientId); - if (deadEvent != null) { - deadEvent.numUnfinishedStarts = 1; - } - - finished(clientId); - } - } - - /** - * Notify that the state of the uid changed - * - * @param newState The new state - */ - public void onUidStateChanged(@AppOpsManager.UidState int newState) { - if (!isPaused() && !isRunning()) { - return; - } - - boolean isRunning = isRunning(); - ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events = - isRunning ? mInProgressEvents : mPausedInProgressEvents; - - int numInProgressEvents = events.size(); - List<IBinder> binders = new ArrayList<>(events.keySet()); - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = events.get(binders.get(i)); - - if (event != null && event.getUidState() != newState) { - try { - // Remove all but one unfinished start count and then call finished() to - // remove start event object - int numPreviousUnfinishedStarts = event.numUnfinishedStarts; - event.numUnfinishedStarts = 1; - OpEventProxyInfo proxy = event.getProxy(); - - finished(event.getClientId(), false); - - // Call started() to add a new start event object and then add the - // previously removed unfinished start counts back - if (proxy != null) { - startedOrPaused(event.getClientId(), proxy.getUid(), - proxy.getPackageName(), proxy.getAttributionTag(), newState, - event.getFlags(), false, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); - } else { - startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, - newState, event.getFlags(), false, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); - } - - events = isRunning ? mInProgressEvents : mPausedInProgressEvents; - InProgressStartOpEvent newEvent = events.get(binders.get(i)); - if (newEvent != null) { - newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1; - } - } catch (RemoteException e) { - if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState); - } - } - } - } - - /** - * Combine {@code a} and {@code b} and return the result. The result might be {@code a} - * or {@code b}. If there is an event for the same key in both the later event is retained. - */ - private @Nullable LongSparseArray<NoteOpEvent> add(@Nullable LongSparseArray<NoteOpEvent> a, - @Nullable LongSparseArray<NoteOpEvent> b) { - if (a == null) { - return b; - } - - if (b == null) { - return a; - } - - int numEventsToAdd = b.size(); - for (int i = 0; i < numEventsToAdd; i++) { - long keyOfEventToAdd = b.keyAt(i); - NoteOpEvent bEvent = b.valueAt(i); - NoteOpEvent aEvent = a.get(keyOfEventToAdd); - - if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) { - a.put(keyOfEventToAdd, bEvent); - } - } - - return a; - } - - /** - * Add all data from the {@code opToAdd} to this op. - * - * <p>If there is an event for the same key in both the later event is retained. - * <p>{@code opToAdd} should not be used after this method is called. - * - * @param opToAdd The op to add - */ - public void add(@NonNull AttributedOp opToAdd) { - if (opToAdd.isRunning() || opToAdd.isPaused()) { - ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = opToAdd.isRunning() - ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; - Slog.w(TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " - + opToAdd.isRunning()); - - int numInProgressEvents = ignoredEvents.size(); - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = ignoredEvents.valueAt(i); - - event.finish(); - mInProgressStartOpEventPool.release(event); - } - } - - mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents); - mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents); - } - - public boolean isRunning() { - return mInProgressEvents != null && !mInProgressEvents.isEmpty(); - } - - public boolean isPaused() { - return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty(); - } - - boolean hasAnyTime() { - return (mAccessEvents != null && mAccessEvents.size() > 0) - || (mRejectEvents != null && mRejectEvents.size() > 0); - } - - /** - * Clone a {@link LongSparseArray} and clone all values. - */ - private @Nullable LongSparseArray<NoteOpEvent> deepClone( - @Nullable LongSparseArray<NoteOpEvent> original) { - if (original == null) { - return original; - } - - int size = original.size(); - LongSparseArray<NoteOpEvent> clone = new LongSparseArray<>(size); - for (int i = 0; i < size; i++) { - clone.put(original.keyAt(i), new NoteOpEvent(original.valueAt(i))); - } - - return clone; - } - - @NonNull AttributedOpEntry createAttributedOpEntryLocked() { - LongSparseArray<NoteOpEvent> accessEvents = deepClone(mAccessEvents); - - // Add in progress events as access events - if (isRunning()) { - long now = SystemClock.elapsedRealtime(); - int numInProgressEvents = mInProgressEvents.size(); - - if (accessEvents == null) { - accessEvents = new LongSparseArray<>(numInProgressEvents); - } - - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - - accessEvents.append(makeKey(event.getUidState(), event.getFlags()), - new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(), - event.getProxy())); - } - } - - LongSparseArray<NoteOpEvent> rejectEvents = deepClone(mRejectEvents); - - return new AttributedOpEntry(parent.op, isRunning(), accessEvents, rejectEvents); - } - } - final class Op { int op; int uid; @@ -1425,7 +637,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch attributedOp = mAttributions.get(attributionTag); if (attributedOp == null) { - attributedOp = new AttributedOp(attributionTag, parent); + attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent); mAttributions.put(attributionTag, attributedOp); } @@ -1675,7 +887,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. */ - private static void onClientDeath(@NonNull AttributedOp attributedOp, + static void onClientDeath(@NonNull AttributedOp attributedOp, @NonNull IBinder clientId) { attributedOp.onClientDeath(clientId); } @@ -1916,7 +1128,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId()); if (isSamplingTarget(pi)) { - synchronized (this) { + synchronized (AppOpsService.this) { mRarelyUsedPackages.add(packageName); } } @@ -4016,7 +3228,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull + void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { ArraySet<ActiveCallback> dispatchedCallbacks = null; @@ -4069,7 +3281,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, + void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, String attributionTag, @OpFlags int flags, @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType, @AttributionFlags int attributionFlags, int attributionChainId) { @@ -5700,10 +4912,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch long maxNumStarts = 0; int numInProgressEvents = attributedOp.mInProgressEvents.size(); for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = attributedOp.mInProgressEvents.valueAt(i); + AttributedOp.InProgressStartOpEvent event = + attributedOp.mInProgressEvents.valueAt(i); earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); - maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts); + maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts); } pw.print(prefix + "Running start at: "); diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java new file mode 100644 index 000000000000..dcc36bcf6149 --- /dev/null +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -0,0 +1,870 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; +import static android.app.AppOpsManager.makeKey; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.LongSparseArray; +import android.util.Pools; +import android.util.Slog; + +import com.android.internal.util.function.pooled.PooledLambda; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +final class AttributedOp { + private final @NonNull AppOpsService mAppOpsService; + public final @Nullable String tag; + public final @NonNull AppOpsService.Op parent; + + /** + * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination + * + * <p>Key is {@link AppOpsManager#makeKey} + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents; + + /** + * Last rejected accesses for each uidState/opFlag combination + * + * <p>Key is {@link AppOpsManager#makeKey} + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents; + + /** + * Currently in progress startOp events + * + * <p>Key is clientId + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents; + + /** + * Currently paused startOp events + * + * <p>Key is clientId + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents; + + AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, + @NonNull AppOpsService.Op parent) { + mAppOpsService = appOpsService; + this.tag = tag; + this.parent = parent; + } + + /** + * Update state when noteOp was rejected or startOp->finishOp event finished + * + * @param proxyUid The uid of the proxy + * @param proxyPackageName The package name of the proxy + * @param proxyAttributionTag the attributionTag in the proxies package + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + public void accessed(int proxyUid, @Nullable String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { + long accessTime = System.currentTimeMillis(); + accessed(accessTime, -1, proxyUid, proxyPackageName, + proxyAttributionTag, uidState, flags); + + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, uidState, flags, accessTime, + AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); + } + + /** + * Add an access that was previously collected. + * + * @param noteTime The time of the event + * @param duration The duration of the event + * @param proxyUid The uid of the proxy + * @param proxyPackageName The package name of the proxy + * @param proxyAttributionTag the attributionTag in the proxies package + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void accessed(long noteTime, long duration, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + long key = makeKey(uidState, flags); + + if (mAccessEvents == null) { + mAccessEvents = new LongSparseArray<>(1); + } + + AppOpsManager.OpEventProxyInfo proxyInfo = null; + if (proxyUid != Process.INVALID_UID) { + proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyAttributionTag); + } + + AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, duration, proxyInfo, + mAppOpsService.mOpEventProxyInfoPool); + } else { + mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo)); + } + } + + /** + * Update state when noteOp/startOp was rejected. + * + * @param uidState UID state of the app noteOp is called for + * @param flags OpFlags of the call + */ + public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + rejected(System.currentTimeMillis(), uidState, flags); + + mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, + parent.packageName, tag, uidState, flags); + } + + /** + * Add an rejection that was previously collected + * + * @param noteTime The time of the event + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void rejected(long noteTime, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { + long key = makeKey(uidState, flags); + + if (mRejectEvents == null) { + mRejectEvents = new LongSparseArray<>(1); + } + + // We do not collect proxy information for rejections yet + AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool); + } else { + mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null)); + } + } + + /** + * Update state when start was called + * + * @param clientId Id of the startOp caller + * @param proxyUid The UID of the proxy app + * @param proxyPackageName The package name of the proxy app + * @param proxyAttributionTag The attribution tag of the proxy app + * @param uidState UID state of the app startOp is called for + * @param flags The proxy flags + * @param attributionFlags The attribution flags associated with this operation. + * @param attributionChainId The if of the attribution chain this operations is a part of. + */ + public void started(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, + uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags, + attributionChainId); + } + + private void started(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) throws RemoteException { + startedOrPaused(clientId, proxyUid, proxyPackageName, + proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded, + /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId); + } + + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, attributionFlags, attributionChainId); + } + + if (isStarted && mInProgressEvents == null) { + mInProgressEvents = new ArrayMap<>(1); + } else if (!isStarted && mPausedInProgressEvents == null) { + mPausedInProgressEvents = new ArrayMap<>(1); + } + ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted + ? mInProgressEvents : mPausedInProgressEvents; + + long startTime = System.currentTimeMillis(); + InProgressStartOpEvent event = events.get(clientId); + if (event == null) { + event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, + SystemClock.elapsedRealtime(), clientId, tag, + PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), + proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + attributionFlags, attributionChainId); + events.put(clientId, event); + } else { + if (uidState != event.getUidState()) { + onUidStateChanged(uidState); + } + } + + event.mNumUnfinishedStarts++; + + if (isStarted) { + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, uidState, flags, startTime, attributionFlags, + attributionChainId); + } + } + + /** + * Update state when finishOp was called. Will finish started ops, and delete paused ops. + * + * @param clientId Id of the finishOp caller + */ + public void finished(@NonNull IBinder clientId) { + finished(clientId, true); + } + + private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) { + finishOrPause(clientId, triggerCallbackIfNeeded, false); + } + + /** + * Update state when paused or finished is called. If pausing, it records the op as + * stopping in the HistoricalRegistry, but does not delete it. + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded, + boolean isPausing) { + int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1; + if (indexOfToken < 0) { + finishPossiblyPaused(clientId, isPausing); + return; + } + + InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); + if (!isPausing) { + event.mNumUnfinishedStarts--; + } + // If we are pausing, create a NoteOpEvent, but don't change the InProgress event + if (event.mNumUnfinishedStarts == 0 || isPausing) { + if (!isPausing) { + event.finish(); + mInProgressEvents.removeAt(indexOfToken); + } + + if (mAccessEvents == null) { + mAccessEvents = new LongSparseArray<>(1); + } + + AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null + ? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null; + + long accessDurationMillis = + SystemClock.elapsedRealtime() - event.getStartElapsedTime(); + AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent( + event.getStartTime(), + accessDurationMillis, proxyCopy); + mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), + finishedEvent); + + mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, + parent.packageName, tag, event.getUidState(), + event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), + event.getAttributionFlags(), event.getAttributionChainId()); + + if (!isPausing) { + mAppOpsService.mInProgressStartOpEventPool.release(event); + if (mInProgressEvents.isEmpty()) { + mInProgressEvents = null; + + // TODO ntmyren: Also callback for single attribution tag activity changes + if (triggerCallbackIfNeeded && !parent.isRunning()) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, + parent.uid, parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); + } + } + } + } + } + + // Finish or pause (no-op) an already paused op + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { + if (!isPaused()) { + Slog.wtf(AppOpsService.TAG, "No ops running or paused"); + return; + } + + int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); + if (indexOfToken < 0) { + Slog.wtf(AppOpsService.TAG, "No op running or paused for the client"); + return; + } else if (isPausing) { + // already paused + return; + } + + // no need to record a paused event finishing. + InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); + event.mNumUnfinishedStarts--; + if (event.mNumUnfinishedStarts == 0) { + mPausedInProgressEvents.removeAt(indexOfToken); + mAppOpsService.mInProgressStartOpEventPool.release(event); + if (mPausedInProgressEvents.isEmpty()) { + mPausedInProgressEvents = null; + } + } + } + + /** + * Create an event that will be started, if the op is unpaused. + */ + public void createPaused(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, + uidState, flags, true, false, attributionFlags, attributionChainId); + } + + /** + * Pause all currently started ops. This will create a HistoricalRegistry + */ + public void pause() { + if (!isRunning()) { + return; + } + + if (mPausedInProgressEvents == null) { + mPausedInProgressEvents = new ArrayMap<>(1); + } + + for (int i = 0; i < mInProgressEvents.size(); i++) { + InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + mPausedInProgressEvents.put(event.getClientId(), event); + finishOrPause(event.getClientId(), true, true); + + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); + } + mInProgressEvents = null; + } + + /** + * Unpause all currently paused ops. This will reinitialize their start and duration + * times, but keep all other values the same + */ + public void resume() { + if (!isPaused()) { + return; + } + + if (mInProgressEvents == null) { + mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size()); + } + boolean shouldSendActive = !mPausedInProgressEvents.isEmpty() + && mInProgressEvents.isEmpty(); + + long startTime = System.currentTimeMillis(); + for (int i = 0; i < mPausedInProgressEvents.size(); i++) { + InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); + mInProgressEvents.put(event.getClientId(), event); + event.setStartElapsedTime(SystemClock.elapsedRealtime()); + event.setStartTime(startTime); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, + event.getAttributionFlags(), event.getAttributionChainId()); + if (shouldSendActive) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, event.getAttributionFlags(), + event.getAttributionChainId()); + } + // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND + // TODO ntmyren: figure out how to get the real mode. + mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, + event.getAttributionFlags(), event.getAttributionChainId()); + } + mPausedInProgressEvents = null; + } + + /** + * Called in the case the client dies without calling finish first + * + * @param clientId The client that died + */ + void onClientDeath(@NonNull IBinder clientId) { + synchronized (mAppOpsService) { + if (!isPaused() && !isRunning()) { + return; + } + + ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused() + ? mPausedInProgressEvents : mInProgressEvents; + InProgressStartOpEvent deadEvent = events.get(clientId); + if (deadEvent != null) { + deadEvent.mNumUnfinishedStarts = 1; + } + + finished(clientId); + } + } + + /** + * Notify that the state of the uid changed + * + * @param newState The new state + */ + public void onUidStateChanged(@AppOpsManager.UidState int newState) { + if (!isPaused() && !isRunning()) { + return; + } + + boolean isRunning = isRunning(); + ArrayMap<IBinder, InProgressStartOpEvent> events = + isRunning ? mInProgressEvents : mPausedInProgressEvents; + + int numInProgressEvents = events.size(); + List<IBinder> binders = new ArrayList<>(events.keySet()); + for (int i = 0; i < numInProgressEvents; i++) { + InProgressStartOpEvent event = events.get(binders.get(i)); + + if (event != null && event.getUidState() != newState) { + try { + // Remove all but one unfinished start count and then call finished() to + // remove start event object + int numPreviousUnfinishedStarts = event.mNumUnfinishedStarts; + event.mNumUnfinishedStarts = 1; + AppOpsManager.OpEventProxyInfo proxy = event.getProxy(); + + finished(event.getClientId(), false); + + // Call started() to add a new start event object and then add the + // previously removed unfinished start counts back + if (proxy != null) { + startedOrPaused(event.getClientId(), proxy.getUid(), + proxy.getPackageName(), proxy.getAttributionTag(), newState, + event.getFlags(), false, isRunning, + event.getAttributionFlags(), event.getAttributionChainId()); + } else { + startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, + newState, event.getFlags(), false, isRunning, + event.getAttributionFlags(), event.getAttributionChainId()); + } + + events = isRunning ? mInProgressEvents : mPausedInProgressEvents; + InProgressStartOpEvent newEvent = events.get(binders.get(i)); + if (newEvent != null) { + newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1; + } + } catch (RemoteException e) { + if (AppOpsService.DEBUG) { + Slog.e(AppOpsService.TAG, + "Cannot switch to new uidState " + newState); + } + } + } + } + } + + /** + * Combine {@code a} and {@code b} and return the result. The result might be {@code a} + * or {@code b}. If there is an event for the same key in both the later event is retained. + */ + private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> add( + @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> a, + @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b) { + if (a == null) { + return b; + } + + if (b == null) { + return a; + } + + int numEventsToAdd = b.size(); + for (int i = 0; i < numEventsToAdd; i++) { + long keyOfEventToAdd = b.keyAt(i); + AppOpsManager.NoteOpEvent bEvent = b.valueAt(i); + AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd); + + if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) { + a.put(keyOfEventToAdd, bEvent); + } + } + + return a; + } + + /** + * Add all data from the {@code opToAdd} to this op. + * + * <p>If there is an event for the same key in both the later event is retained. + * <p>{@code opToAdd} should not be used after this method is called. + * + * @param opToAdd The op to add + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void add(@NonNull AttributedOp opToAdd) { + if (opToAdd.isRunning() || opToAdd.isPaused()) { + ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = + opToAdd.isRunning() + ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; + Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " + + opToAdd.isRunning()); + + int numInProgressEvents = ignoredEvents.size(); + for (int i = 0; i < numInProgressEvents; i++) { + InProgressStartOpEvent event = ignoredEvents.valueAt(i); + + event.finish(); + mAppOpsService.mInProgressStartOpEventPool.release(event); + } + } + + mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents); + mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents); + } + + public boolean isRunning() { + return mInProgressEvents != null && !mInProgressEvents.isEmpty(); + } + + public boolean isPaused() { + return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty(); + } + + boolean hasAnyTime() { + return (mAccessEvents != null && mAccessEvents.size() > 0) + || (mRejectEvents != null && mRejectEvents.size() > 0); + } + + /** + * Clone a {@link LongSparseArray} and clone all values. + */ + private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> deepClone( + @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> original) { + if (original == null) { + return original; + } + + int size = original.size(); + LongSparseArray<AppOpsManager.NoteOpEvent> clone = new LongSparseArray<>(size); + for (int i = 0; i < size; i++) { + clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i))); + } + + return clone; + } + + @NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() { + LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = deepClone(mAccessEvents); + + // Add in progress events as access events + if (isRunning()) { + long now = SystemClock.elapsedRealtime(); + int numInProgressEvents = mInProgressEvents.size(); + + if (accessEvents == null) { + accessEvents = new LongSparseArray<>(numInProgressEvents); + } + + for (int i = 0; i < numInProgressEvents; i++) { + InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + + accessEvents.append(makeKey(event.getUidState(), event.getFlags()), + new AppOpsManager.NoteOpEvent(event.getStartTime(), + now - event.getStartElapsedTime(), + event.getProxy())); + } + } + + LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = deepClone(mRejectEvents); + + return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents, + rejectEvents); + } + + /** A in progress startOp->finishOp event */ + static final class InProgressStartOpEvent implements IBinder.DeathRecipient { + /** Wall clock time of startOp event (not monotonic) */ + private long mStartTime; + + /** Elapsed time since boot of startOp event */ + private long mStartElapsedTime; + + /** Id of the client that started the event */ + private @NonNull IBinder mClientId; + + /** The attribution tag for this operation */ + private @Nullable String mAttributionTag; + + /** To call when client dies */ + private @NonNull Runnable mOnDeath; + + /** uidstate used when calling startOp */ + private @AppOpsManager.UidState int mUidState; + + /** Proxy information of the startOp event */ + private @Nullable AppOpsManager.OpEventProxyInfo mProxy; + + /** Proxy flag information */ + private @AppOpsManager.OpFlags int mFlags; + + /** How many times the op was started but not finished yet */ + int mNumUnfinishedStarts; + + /** The attribution flags related to this event */ + private @AppOpsManager.AttributionFlags int mAttributionFlags; + + /** The id of the attribution chain this even is a part of */ + private int mAttributionChainId; + + /** + * Create a new {@link InProgressStartOpEvent}. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for the operation. + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. + * @param proxy The proxy information, if {@link #startProxyOperation} was + * called + * @param flags The trusted/nontrusted/self flags. + * @throws RemoteException If the client is dying + */ + InProgressStartOpEvent(long startTime, long startElapsedTime, + @NonNull IBinder clientId, @Nullable String attributionTag, + @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, + @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) + throws RemoteException { + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mAttributionTag = attributionTag; + mOnDeath = onDeath; + mUidState = uidState; + mProxy = proxy; + mFlags = flags; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; + + clientId.linkToDeath(this, 0); + } + + /** Clean up event */ + public void finish() { + try { + mClientId.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + // Either not linked, or already unlinked. Either way, nothing to do. + } + } + + @Override + public void binderDied() { + mOnDeath.run(); + } + + /** + * Reinit existing object with new state. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for this operation. + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param flags The flags relating to the proxy + * @param proxy The proxy information, if {@link #startProxyOperation} + * was called + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. + * @param proxyPool The pool to release + * previous {@link AppOpsManager.OpEventProxyInfo} to + * @throws RemoteException If the client is dying + */ + public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, + @Nullable String attributionTag, @NonNull Runnable onDeath, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @Nullable AppOpsManager.OpEventProxyInfo proxy, + @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, + @NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool + ) throws RemoteException { + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mAttributionTag = attributionTag; + mOnDeath = onDeath; + mUidState = uidState; + mFlags = flags; + + if (mProxy != null) { + proxyPool.release(mProxy); + } + mProxy = proxy; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; + + clientId.linkToDeath(this, 0); + } + + /** @return Wall clock time of startOp event */ + public long getStartTime() { + return mStartTime; + } + + /** @return Elapsed time since boot of startOp event */ + public long getStartElapsedTime() { + return mStartElapsedTime; + } + + /** @return Id of the client that started the event */ + public @NonNull IBinder getClientId() { + return mClientId; + } + + /** @return uidstate used when calling startOp */ + public @AppOpsManager.UidState int getUidState() { + return mUidState; + } + + /** @return proxy tag for the access */ + public @Nullable AppOpsManager.OpEventProxyInfo getProxy() { + return mProxy; + } + + /** @return flags used for the access */ + public @AppOpsManager.OpFlags int getFlags() { + return mFlags; + } + + /** @return attributoin flags used for the access */ + public @AppOpsManager.AttributionFlags int getAttributionFlags() { + return mAttributionFlags; + } + + /** @return attribution chain id for the access */ + public int getAttributionChainId() { + return mAttributionChainId; + } + + public void setStartTime(long startTime) { + mStartTime = startTime; + } + + public void setStartElapsedTime(long startElapsedTime) { + mStartElapsedTime = startElapsedTime; + } + } + + /** + * An unsynchronized pool of {@link InProgressStartOpEvent} objects. + */ + static class InProgressStartOpEventPool extends Pools.SimplePool<InProgressStartOpEvent> { + private OpEventProxyInfoPool mOpEventProxyInfoPool; + + InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool, + int maxUnusedPooledObjects) { + super(maxUnusedPooledObjects); + this.mOpEventProxyInfoPool = opEventProxyInfoPool; + } + + InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, + @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + + InProgressStartOpEvent recycled = acquire(); + + AppOpsManager.OpEventProxyInfo proxyInfo = null; + if (proxyUid != Process.INVALID_UID) { + proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyAttributionTag); + } + + if (recycled != null) { + recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath, + uidState, flags, proxyInfo, attributionFlags, attributionChainId, + mOpEventProxyInfoPool); + return recycled; + } + + return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag, + onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId); + } + } + + /** + * An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects. + */ + static class OpEventProxyInfoPool extends Pools.SimplePool<AppOpsManager.OpEventProxyInfo> { + OpEventProxyInfoPool(int maxUnusedPooledObjects) { + super(maxUnusedPooledObjects); + } + + AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid, + @Nullable String packageName, + @Nullable String attributionTag) { + AppOpsManager.OpEventProxyInfo recycled = acquire(); + if (recycled != null) { + recycled.reinit(uid, packageName, attributionTag); + return recycled; + } + + return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag); + } + } +} diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 676dc196f20c..6a53978175af 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3210,41 +3210,44 @@ public class Vpn { return; } - if (mSession != null && mMobikeEnabled) { - Log.d( - TAG, - "IKE Session has mobility. Delay handleSessionLost for losing network " - + network - + " on session with token " - + mCurrentToken); - - final int token = mCurrentToken; - // Delay the teardown in case a new network will be available soon. For example, - // during handover between two WiFi networks, Android will disconnect from the - // first WiFi and then connects to the second WiFi. - mScheduledHandleNetworkLostFuture = - mExecutor.schedule( - () -> { - if (isActiveToken(token)) { - handleSessionLost(null /* exception */, network); - } else { - Log.d( - TAG, - "Scheduled handleSessionLost fired for " - + "obsolete token " - + token); + Log.d(TAG, "Schedule a delay handleSessionLost for losing network " + + network + + " on session with token " + + mCurrentToken); + + final int token = mCurrentToken; + // Delay the teardown in case a new network will be available soon. For example, + // during handover between two WiFi networks, Android will disconnect from the + // first WiFi and then connects to the second WiFi. + mScheduledHandleNetworkLostFuture = + mExecutor.schedule( + () -> { + if (isActiveToken(token)) { + handleSessionLost(new IkeNetworkLostException(network), + network); + + synchronized (Vpn.this) { + // Ignore stale runner. + if (mVpnRunner != this) return; + + updateState(DetailedState.DISCONNECTED, + "Network lost"); } + } else { + Log.d( + TAG, + "Scheduled handleSessionLost fired for " + + "obsolete token " + + token); + } + + // Reset mScheduledHandleNetworkLostFuture since it's + // already run on executor thread. + mScheduledHandleNetworkLostFuture = null; + }, + NETWORK_LOST_TIMEOUT_MS, + TimeUnit.MILLISECONDS); - // Reset mScheduledHandleNetworkLostFuture since it's - // already run on executor thread. - mScheduledHandleNetworkLostFuture = null; - }, - NETWORK_LOST_TIMEOUT_MS, - TimeUnit.MILLISECONDS); - } else { - Log.d(TAG, "Call handleSessionLost for losing network " + network); - handleSessionLost(null /* exception */, network); - } } private void cancelHandleNetworkLostTimeout() { @@ -4185,8 +4188,6 @@ public class Vpn { */ @NonNull public synchronized List<String> getAppExclusionList(@NonNull String packageName) { - enforceNotRestrictedUser(); - final long oldId = Binder.clearCallingIdentity(); try { final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName)); diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 8e00ccf68fb1..44c8e18a22cf 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -21,8 +21,11 @@ import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STA import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; +import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN; import android.annotation.IntDef; import android.annotation.IntRange; @@ -106,6 +109,8 @@ public final class DeviceStateManagerService extends SystemService { private final BinderService mBinderService; @NonNull private final OverrideRequestController mOverrideRequestController; + @NonNull + private final DeviceStateProviderListener mDeviceStateProviderListener; @VisibleForTesting @NonNull public ActivityTaskManagerInternal mActivityTaskManagerInternal; @@ -139,6 +144,12 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private Optional<OverrideRequest> mActiveOverride = Optional.empty(); + // The current active base state override request. When set the device state specified here will + // replace the value in mBaseState. + @GuardedBy("mLock") + @NonNull + private Optional<OverrideRequest> mActiveBaseStateOverride = Optional.empty(); + // List of processes registered to receive notifications about changes to device state and // request status indexed by process id. @GuardedBy("mLock") @@ -177,7 +188,8 @@ public final class DeviceStateManagerService extends SystemService { mOverrideRequestController = new OverrideRequestController( this::onOverrideRequestStatusChangedLocked); mDeviceStatePolicy = policy; - mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); + mDeviceStateProviderListener = new DeviceStateProviderListener(); + mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener); mBinderService = new BinderService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); } @@ -257,6 +269,21 @@ public final class DeviceStateManagerService extends SystemService { } } + /** + * Returns the current override base state, or {@link Optional#empty()} if no override state is + * requested. If an override base state is present, the returned state will be the same as + * the base state returned from {@link #getBaseState()}. + */ + @NonNull + Optional<DeviceState> getOverrideBaseState() { + synchronized (mLock) { + if (mActiveBaseStateOverride.isPresent()) { + return getStateLocked(mActiveBaseStateOverride.get().getRequestedState()); + } + return Optional.empty(); + } + } + /** Returns the list of currently supported device states. */ DeviceState[] getSupportedStates() { synchronized (mLock) { @@ -366,6 +393,7 @@ public final class DeviceStateManagerService extends SystemService { } final DeviceState baseState = baseStateOptional.get(); + if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) { // Base state hasn't changed. Nothing to do. return; @@ -375,7 +403,7 @@ public final class DeviceStateManagerService extends SystemService { if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { mOverrideRequestController.cancelOverrideRequest(); } - mOverrideRequestController.handleBaseStateChanged(); + mOverrideRequestController.handleBaseStateChanged(identifier); updatePendingStateLocked(); if (!mPendingState.isPresent()) { @@ -528,16 +556,41 @@ public final class DeviceStateManagerService extends SystemService { } } + @GuardedBy("mLock") private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, @OverrideRequestController.RequestStatus int status) { - if (status == STATUS_ACTIVE) { - mActiveOverride = Optional.of(request); - } else if (status == STATUS_CANCELED) { - if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { - mActiveOverride = Optional.empty(); + if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) { + switch (status) { + case STATUS_ACTIVE: + enableBaseStateRequestLocked(request); + return; + case STATUS_CANCELED: + if (mActiveBaseStateOverride.isPresent() + && mActiveBaseStateOverride.get() == request) { + mActiveBaseStateOverride = Optional.empty(); + } + break; + case STATUS_UNKNOWN: // same as default + default: + throw new IllegalArgumentException("Unknown request status: " + status); + } + } else if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_EMULATED_STATE) { + switch (status) { + case STATUS_ACTIVE: + mActiveOverride = Optional.of(request); + break; + case STATUS_CANCELED: + if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { + mActiveOverride = Optional.empty(); + } + break; + case STATUS_UNKNOWN: // same as default + default: + throw new IllegalArgumentException("Unknown request status: " + status); } } else { - throw new IllegalArgumentException("Unknown request status: " + status); + throw new IllegalArgumentException( + "Unknown OverrideRest type: " + request.getRequestType()); } boolean updatedPendingState = updatePendingStateLocked(); @@ -564,6 +617,18 @@ public final class DeviceStateManagerService extends SystemService { mHandler.post(this::notifyPolicyIfNeeded); } + /** + * Sets the new base state of the device and notifies the process that made the base state + * override request that the request is now active. + */ + @GuardedBy("mLock") + private void enableBaseStateRequestLocked(OverrideRequest request) { + setBaseState(request.getRequestedState()); + mActiveBaseStateOverride = Optional.of(request); + ProcessRecord processRecord = mProcessRecords.get(request.getPid()); + processRecord.notifyRequestActiveAsync(request.getToken()); + } + private void registerProcess(int pid, IDeviceStateManagerCallback callback) { synchronized (mLock) { if (mProcessRecords.contains(pid)) { @@ -606,7 +671,8 @@ public final class DeviceStateManagerService extends SystemService { + " has no registered callback."); } - if (mOverrideRequestController.hasRequest(token)) { + if (mOverrideRequestController.hasRequest(token, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE)) { throw new IllegalStateException("Request has already been made for the supplied" + " token: " + token); } @@ -617,7 +683,8 @@ public final class DeviceStateManagerService extends SystemService { + " is not supported."); } - OverrideRequest request = new OverrideRequest(token, callingPid, state, flags); + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mOverrideRequestController.addRequest(request); } } @@ -633,6 +700,44 @@ public final class DeviceStateManagerService extends SystemService { } } + private void requestBaseStateOverrideInternal(int state, int flags, int callingPid, + @NonNull IBinder token) { + synchronized (mLock) { + final Optional<DeviceState> deviceState = getStateLocked(state); + if (!deviceState.isPresent()) { + throw new IllegalArgumentException("Requested state: " + state + + " is not supported."); + } + + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + + if (mOverrideRequestController.hasRequest(token, + OVERRIDE_REQUEST_TYPE_BASE_STATE)) { + throw new IllegalStateException("Request has already been made for the supplied" + + " token: " + token); + } + + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags, + OVERRIDE_REQUEST_TYPE_BASE_STATE); + mOverrideRequestController.addBaseStateRequest(request); + } + } + + private void cancelBaseStateOverrideInternal(int callingPid) { + synchronized (mLock) { + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + setBaseState(mDeviceStateProviderListener.mCurrentBaseState); + } + } + private void dumpInternal(PrintWriter pw) { pw.println("DEVICE STATE MANAGER (dumpsys device_state)"); @@ -729,6 +834,8 @@ public final class DeviceStateManagerService extends SystemService { } private final class DeviceStateProviderListener implements DeviceStateProvider.Listener { + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState; + @Override public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) { if (newDeviceStates.length == 0) { @@ -743,7 +850,7 @@ public final class DeviceStateManagerService extends SystemService { if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) { throw new IllegalArgumentException("Invalid identifier: " + identifier); } - + mCurrentBaseState = identifier; setBaseState(identifier); } } @@ -895,6 +1002,38 @@ public final class DeviceStateManagerService extends SystemService { } @Override // Binder call + public void requestBaseStateOverride(IBinder token, int state, int flags) { + final int callingPid = Binder.getCallingPid(); + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to control base state of device."); + + if (token == null) { + throw new IllegalArgumentException("Request token must not be null."); + } + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + requestBaseStateOverrideInternal(state, flags, callingPid, token); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call + public void cancelBaseStateOverride() { + final int callingPid = Binder.getCallingPid(); + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to control base state of device."); + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + cancelBaseStateOverrideInternal(callingPid); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new DeviceStateManagerShellCommand(DeviceStateManagerService.this) diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index 659ee75de453..8c6068d89296 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -16,11 +16,8 @@ package com.android.server.devicestate; -import static android.Manifest.permission.CONTROL_DEVICE_STATE; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; import android.os.Binder; @@ -39,6 +36,8 @@ import java.util.stream.Collectors; public class DeviceStateManagerShellCommand extends ShellCommand { @Nullable private static DeviceStateRequest sLastRequest; + @Nullable + private static DeviceStateRequest sLastBaseStateRequest; private final DeviceStateManagerService mService; private final DeviceStateManager mClient; @@ -58,6 +57,8 @@ public class DeviceStateManagerShellCommand extends ShellCommand { switch (cmd) { case "state": return runState(pw); + case "base-state": + return runBaseState(pw); case "print-state": return runPrintState(pw); case "print-states": @@ -89,10 +90,6 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } - final Context context = mService.getContext(); - context.enforceCallingOrSelfPermission( - CONTROL_DEVICE_STATE, - "Permission required to request device state."); final long callingIdentity = Binder.clearCallingIdentity(); try { if ("reset".equals(nextArg)) { @@ -127,6 +124,47 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } + private int runBaseState(PrintWriter pw) { + final String nextArg = getNextArg(); + if (nextArg == null) { + printAllStates(pw); + return 0; + } + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + if ("reset".equals(nextArg)) { + if (sLastBaseStateRequest != null) { + mClient.cancelBaseStateOverride(); + sLastBaseStateRequest = null; + } + } else { + int requestedState = Integer.parseInt(nextArg); + DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build(); + + mClient.requestBaseStateOverride(request, null, null); + + sLastBaseStateRequest = request; + } + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: requested state should be an integer"); + return -1; + } catch (IllegalArgumentException e) { + getErrPrintWriter().println("Error: " + e.getMessage()); + getErrPrintWriter().println("-------------------"); + getErrPrintWriter().println("Run:"); + getErrPrintWriter().println(""); + getErrPrintWriter().println(" print-states"); + getErrPrintWriter().println(""); + getErrPrintWriter().println("to get the list of currently supported device states"); + return -1; + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + + return 0; + } + private int runPrintState(PrintWriter pw) { Optional<DeviceState> deviceState = mService.getCommittedState(); if (deviceState.isPresent()) { diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 35a4c844c710..325e384cb8ad 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequest.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -16,9 +16,13 @@ package com.android.server.devicestate; +import android.annotation.IntDef; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A request to override the state managed by {@link DeviceStateManagerService}. * @@ -30,13 +34,47 @@ final class OverrideRequest { private final int mRequestedState; @DeviceStateRequest.RequestFlags private final int mFlags; + @OverrideRequestType + private final int mRequestType; + + /** + * Denotes that the request is meant to override the emulated state of the device. This will + * not change the base (physical) state of the device. + * + * This request type should be used if you are looking to emulate a device state for a feature + * but want the system to be aware of the physical state of the device. + */ + public static final int OVERRIDE_REQUEST_TYPE_EMULATED_STATE = 0; + + /** + * Denotes that the request is meant to override the base (physical) state of the device. + * Overriding the base state may not change the emulated state of the device if there is also an + * override request active for that property. + * + * This request type should only be used for testing, where you want to simulate the physical + * state of the device changing. + */ + public static final int OVERRIDE_REQUEST_TYPE_BASE_STATE = 1; + + /** + * Flags for signifying the type of {@link OverrideRequest}. + * + * @hide + */ + @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = { + OVERRIDE_REQUEST_TYPE_BASE_STATE, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OverrideRequestType {} OverrideRequest(IBinder token, int pid, int requestedState, - @DeviceStateRequest.RequestFlags int flags) { + @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) { mToken = token; mPid = pid; mRequestedState = requestedState; mFlags = flags; + mRequestType = requestType; } IBinder getToken() { @@ -55,4 +93,9 @@ final class OverrideRequest { int getFlags() { return mFlags; } + + @OverrideRequestType + int getRequestType() { + return mRequestType; + } } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 01f5a09323cf..e39c7324d7bd 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -75,6 +75,8 @@ final class OverrideRequestController { // Handle to the current override request, null if none. private OverrideRequest mRequest; + // Handle to the current base state override request, null if none. + private OverrideRequest mBaseStateRequest; private boolean mStickyRequestsAllowed; // The current request has outlived their process. @@ -111,13 +113,23 @@ final class OverrideRequestController { } } + void addBaseStateRequest(@NonNull OverrideRequest request) { + OverrideRequest previousRequest = mBaseStateRequest; + mBaseStateRequest = request; + mListener.onStatusChanged(request, STATUS_ACTIVE); + + if (previousRequest != null) { + cancelRequestLocked(previousRequest); + } + } + /** * Cancels the request with the specified {@code token} and notifies the listener of all changes * to request status as a result of this operation. */ void cancelRequest(@NonNull OverrideRequest request) { // Either don't have a current request or attempting to cancel an already cancelled request - if (!hasRequest(request.getToken())) { + if (!hasRequest(request.getToken(), request.getRequestType())) { return; } cancelCurrentRequestLocked(); @@ -144,11 +156,24 @@ final class OverrideRequestController { } /** + * Cancels the current base state override request, this could be due to the physical + * configuration of the device changing. + */ + void cancelBaseStateOverrideRequest() { + cancelCurrentBaseStateRequestLocked(); + } + + /** * Returns {@code true} if this controller is current managing a request with the specified * {@code token}, {@code false} otherwise. */ - boolean hasRequest(@NonNull IBinder token) { - return mRequest != null && token == mRequest.getToken(); + boolean hasRequest(@NonNull IBinder token, + @OverrideRequest.OverrideRequestType int requestType) { + if (requestType == OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE) { + return mBaseStateRequest != null && token == mBaseStateRequest.getToken(); + } else { + return mRequest != null && token == mRequest.getToken(); + } } /** @@ -157,11 +182,11 @@ final class OverrideRequestController { * operation. */ void handleProcessDied(int pid) { - if (mRequest == null) { - return; + if (mBaseStateRequest != null && mBaseStateRequest.getPid() == pid) { + cancelCurrentBaseStateRequestLocked(); } - if (mRequest.getPid() == pid) { + if (mRequest != null && mRequest.getPid() == pid) { if (mStickyRequestsAllowed) { // Do not cancel the requests now because sticky requests are allowed. These // requests will be cancelled on a call to cancelStickyRequests(). @@ -176,7 +201,10 @@ final class OverrideRequestController { * Notifies the controller that the base state has changed. The controller will notify the * listener of all changes to request status as a result of this change. */ - void handleBaseStateChanged() { + void handleBaseStateChanged(int state) { + if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) { + cancelBaseStateOverrideRequest(); + } if (mRequest == null) { return; } @@ -192,11 +220,12 @@ final class OverrideRequestController { * notify the listener of all changes to request status as a result of this change. */ void handleNewSupportedStates(int[] newSupportedStates) { - if (mRequest == null) { - return; + if (mBaseStateRequest != null && !contains(newSupportedStates, + mBaseStateRequest.getRequestedState())) { + cancelCurrentBaseStateRequestLocked(); } - if (!contains(newSupportedStates, mRequest.getRequestedState())) { + if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { cancelCurrentRequestLocked(); } } @@ -228,10 +257,23 @@ final class OverrideRequestController { return; } mStickyRequest = false; - mListener.onStatusChanged(mRequest, STATUS_CANCELED); + cancelRequestLocked(mRequest); mRequest = null; } + /** + * Handles cancelling {@code mBaseStateRequest}. + * Notifies the listener of the canceled status as well. + */ + private void cancelCurrentBaseStateRequestLocked() { + if (mBaseStateRequest == null) { + Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); + return; + } + cancelRequestLocked(mBaseStateRequest); + mBaseStateRequest = null; + } + private static boolean contains(int[] array, int value) { for (int i = 0; i < array.length; i++) { if (array[i] == value) { diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index e9640cf6b4d5..a060f076d4fb 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -16,6 +16,9 @@ package com.android.server.display; +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.os.IBinder; import java.util.Objects; @@ -26,6 +29,7 @@ import java.util.Objects; public class DisplayControl { private static native IBinder nativeCreateDisplay(String name, boolean secure); private static native void nativeDestroyDisplay(IBinder displayToken); + private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); /** * Create a display in SurfaceFlinger. @@ -52,4 +56,11 @@ public class DisplayControl { nativeDestroyDisplay(displayToken); } + /** + * Overrides HDR modes for a display device. + */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) + public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) { + nativeOverrideHdrTypes(displayToken, modes); + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 9ab78bc9cb32..2cde526c6dbf 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2562,10 +2562,10 @@ public final class DisplayManagerService extends SystemService { final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, display, mSyncRoot); - final DisplayPowerController displayPowerController; + final DisplayPowerControllerInterface displayPowerController; if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) { - displayPowerController = new DisplayPowerController( + displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, () -> handleBrightnessChange(display)); @@ -3000,6 +3000,19 @@ public final class DisplayManagerService extends SystemService { } } + @Override + public void overrideHdrTypes(int displayId, int[] modes) { + IBinder displayToken; + synchronized (mSyncRoot) { + displayToken = getDisplayToken(displayId); + if (displayToken == null) { + throw new IllegalArgumentException("Invalid display: " + displayId); + } + } + + DisplayControl.overrideHdrTypes(displayToken, modes); + } + @Override // Binder call public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 58a80e3f977b..8d3e040bf4e6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -798,8 +798,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * Notified when the display is changed. We use this to apply any changes that might be needed * when displays get swapped on foldable devices. For example, different brightness properties * of each display need to be properly reflected in AutomaticBrightnessController. + * + * Make sure DisplayManagerService.mSyncRoot is held when this is called */ - @GuardedBy("DisplayManagerService.mSyncRoot") @Override public void onDisplayChanged() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); @@ -988,8 +989,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.array.config_screenBrighteningThresholds); int[] screenDarkeningThresholds = resources.getIntArray( com.android.internal.R.array.config_screenDarkeningThresholds); - int[] screenThresholdLevels = resources.getIntArray( - com.android.internal.R.array.config_screenThresholdLevels); + float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources + .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels)); float screenDarkeningMinThreshold = mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); float screenBrighteningMinThreshold = @@ -1597,9 +1598,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be // done in HighBrightnessModeController. if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR - && ((mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 - || (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) - == 0)) { + && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 + && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) + == 0) { // We want to scale HDR brightness level with the SDR level animateValue = mHbmController.getHdrBrightnessValue(); } @@ -1896,7 +1897,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }, () -> { - sendUpdatePowerStateLocked(); + sendUpdatePowerState(); postBrightnessChangeRunnable(); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. if (mAutomaticBrightnessController != null) { @@ -1912,7 +1913,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null; return new BrightnessThrottler(mHandler, data, () -> { - sendUpdatePowerStateLocked(); + sendUpdatePowerState(); postBrightnessChangeRunnable(); }, mUniqueDisplayId); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java new file mode 100644 index 000000000000..c734095a4743 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -0,0 +1,3071 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.AmbientBrightnessDayStats; +import android.hardware.display.BrightnessChangeEvent; +import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.metrics.LogMaker; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.FloatProperty; +import android.util.Log; +import android.util.MathUtils; +import android.util.MutableFloat; +import android.util.MutableInt; +import android.util.Slog; +import android.util.TimeUtils; +import android.view.Display; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IBatteryStats; +import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.RingBuffer; +import com.android.server.LocalServices; +import com.android.server.am.BatteryStatsService; +import com.android.server.display.RampAnimator.DualRampAnimator; +import com.android.server.display.brightness.BrightnessEvent; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; +import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; +import com.android.server.display.utils.SensorUtils; +import com.android.server.display.whitebalance.DisplayWhiteBalanceController; +import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; +import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings; +import com.android.server.policy.WindowManagerPolicy; + +import java.io.PrintWriter; + +/** + * Controls the power state of the display. + * + * Handles the proximity sensor, light sensor, and animations between states + * including the screen off animation. + * + * This component acts independently of the rest of the power manager service. + * In particular, it does not share any state and it only communicates + * via asynchronous callbacks to inform the power manager that something has + * changed. + * + * Everything this class does internally is serialized on its handler although + * it may be accessed by other threads from the outside. + * + * Note that the power manager service guarantees that it will hold a suspend + * blocker as long as the display is not ready. So most of the work done here + * does not need to worry about holding a suspend blocker unless it happens + * independently of the display ready signal. + * + * For debugging, you can make the color fade and brightness animations run + * slower by changing the "animator duration scale" option in Development Settings. + */ +final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks, + DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface { + private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; + private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; + + private static final boolean DEBUG = false; + private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; + + // If true, uses the color fade on animation. + // We might want to turn this off if we cannot get a guarantee that the screen + // actually turns on and starts showing new content after the call to set the + // screen state returns. Playing the animation can also be somewhat slow. + private static final boolean USE_COLOR_FADE_ON_ANIMATION = false; + + private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f; + + private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250; + private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400; + + private static final int MSG_UPDATE_POWER_STATE = 1; + private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; + private static final int MSG_SCREEN_ON_UNBLOCKED = 3; + private static final int MSG_SCREEN_OFF_UNBLOCKED = 4; + private static final int MSG_CONFIGURE_BRIGHTNESS = 5; + private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6; + private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7; + private static final int MSG_IGNORE_PROXIMITY = 8; + private static final int MSG_STOP = 9; + private static final int MSG_UPDATE_BRIGHTNESS = 10; + private static final int MSG_UPDATE_RBC = 11; + private static final int MSG_BRIGHTNESS_RAMP_DONE = 12; + private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; + + private static final int PROXIMITY_UNKNOWN = -1; + private static final int PROXIMITY_NEGATIVE = 0; + private static final int PROXIMITY_POSITIVE = 1; + + // Proximity sensor debounce delay in milliseconds for positive or negative transitions. + private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; + private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250; + + private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500; + + // Trigger proximity if distance is less than 5 cm. + private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; + + // State machine constants for tracking initial brightness ramp skipping when enabled. + private static final int RAMP_STATE_SKIP_NONE = 0; + private static final int RAMP_STATE_SKIP_INITIAL = 1; + private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2; + + private static final int REPORTED_TO_POLICY_UNREPORTED = -1; + private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0; + private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1; + private static final int REPORTED_TO_POLICY_SCREEN_ON = 2; + private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3; + + private static final int RINGBUFFER_MAX = 100; + + private final String mTag; + + private final Object mLock = new Object(); + + private final Context mContext; + + // Our handler. + private final DisplayControllerHandler mHandler; + + // Asynchronous callbacks into the power manager service. + // Only invoked from the handler thread while no locks are held. + private final DisplayPowerCallbacks mCallbacks; + + // Battery stats. + @Nullable + private final IBatteryStats mBatteryStats; + + // The sensor manager. + private final SensorManager mSensorManager; + + // The window manager policy. + private final WindowManagerPolicy mWindowManagerPolicy; + + // The display blanker. + private final DisplayBlanker mBlanker; + + // The LogicalDisplay tied to this DisplayPowerController2. + private final LogicalDisplay mLogicalDisplay; + + // The ID of the LogicalDisplay tied to this DisplayPowerController2. + private final int mDisplayId; + + // The unique ID of the primary display device currently tied to this logical display + private String mUniqueDisplayId; + + // Tracker for brightness changes. + @Nullable + private final BrightnessTracker mBrightnessTracker; + + // Tracker for brightness settings changes. + private final SettingsObserver mSettingsObserver; + + // The proximity sensor, or null if not available or needed. + private Sensor mProximitySensor; + + // The doze screen brightness. + private final float mScreenBrightnessDozeConfig; + + // The dim screen brightness. + private final float mScreenBrightnessDimConfig; + + // The minimum dim amount to use if the screen brightness is already below + // mScreenBrightnessDimConfig. + private final float mScreenBrightnessMinimumDimAmount; + + private final float mScreenBrightnessDefault; + + // The minimum allowed brightness while in VR. + private final float mScreenBrightnessForVrRangeMinimum; + + // The maximum allowed brightness while in VR. + private final float mScreenBrightnessForVrRangeMaximum; + + // The default screen brightness for VR. + private final float mScreenBrightnessForVrDefault; + + // True if auto-brightness should be used. + private boolean mUseSoftwareAutoBrightnessConfig; + + // True if should use light sensor to automatically determine doze screen brightness. + private final boolean mAllowAutoBrightnessWhileDozingConfig; + + // Whether or not the color fade on screen on / off is enabled. + private final boolean mColorFadeEnabled; + + @GuardedBy("mCachedBrightnessInfo") + private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo(); + + private DisplayDevice mDisplayDevice; + + // True if we should fade the screen while turning it off, false if we should play + // a stylish color fade animation instead. + private final boolean mColorFadeFadesConfig; + + // True if we need to fake a transition to off when coming out of a doze state. + // Some display hardware will blank itself when coming out of doze in order to hide + // artifacts. For these displays we fake a transition into OFF so that policy can appropriately + // blank itself and begin an appropriate power on animation. + private final boolean mDisplayBlanksAfterDozeConfig; + + // True if there are only buckets of brightness values when the display is in the doze state, + // rather than a full range of values. If this is true, then we'll avoid animating the screen + // brightness since it'd likely be multiple jarring brightness transitions instead of just one + // to reach the final state. + private final boolean mBrightnessBucketsInDozeConfig; + + private final Clock mClock; + private final Injector mInjector; + + // Maximum time a ramp animation can take. + private long mBrightnessRampIncreaseMaxTimeMillis; + private long mBrightnessRampDecreaseMaxTimeMillis; + + // The pending power request. + // Initially null until the first call to requestPowerState. + @GuardedBy("mLock") + private DisplayPowerRequest mPendingRequestLocked; + + // True if a request has been made to wait for the proximity sensor to go negative. + @GuardedBy("mLock") + private boolean mPendingWaitForNegativeProximityLocked; + + // True if the pending power request or wait for negative proximity flag + // has been changed since the last update occurred. + @GuardedBy("mLock") + private boolean mPendingRequestChangedLocked; + + // Set to true when the important parts of the pending power request have been applied. + // The important parts are mainly the screen state. Brightness changes may occur + // concurrently. + @GuardedBy("mLock") + private boolean mDisplayReadyLocked; + + // Set to true if a power state update is required. + @GuardedBy("mLock") + private boolean mPendingUpdatePowerStateLocked; + + /* The following state must only be accessed by the handler thread. */ + + // The currently requested power state. + // The power controller will progressively update its internal state to match + // the requested power state. Initially null until the first update. + private DisplayPowerRequest mPowerRequest; + + // The current power state. + // Must only be accessed on the handler thread. + private DisplayPowerState mPowerState; + + // True if the device should wait for negative proximity sensor before + // waking up the screen. This is set to false as soon as a negative + // proximity sensor measurement is observed or when the device is forced to + // go to sleep by the user. While true, the screen remains off. + private boolean mWaitingForNegativeProximity; + + // True if the device should not take into account the proximity sensor + // until either the proximity sensor state changes, or there is no longer a + // request to listen to proximity sensor. + private boolean mIgnoreProximityUntilChanged; + + // The actual proximity sensor threshold value. + private float mProximityThreshold; + + // Set to true if the proximity sensor listener has been registered + // with the sensor manager. + private boolean mProximitySensorEnabled; + + // The debounced proximity sensor state. + private int mProximity = PROXIMITY_UNKNOWN; + + // The raw non-debounced proximity sensor state. + private int mPendingProximity = PROXIMITY_UNKNOWN; + private long mPendingProximityDebounceTime = -1; // -1 if fully debounced + + // True if the screen was turned off because of the proximity sensor. + // When the screen turns on again, we report user activity to the power manager. + private boolean mScreenOffBecauseOfProximity; + + // The currently active screen on unblocker. This field is non-null whenever + // we are waiting for a callback to release it and unblock the screen. + private ScreenOnUnblocker mPendingScreenOnUnblocker; + private ScreenOffUnblocker mPendingScreenOffUnblocker; + + // True if we were in the process of turning off the screen. + // This allows us to recover more gracefully from situations where we abort + // turning off the screen. + private boolean mPendingScreenOff; + + // True if we have unfinished business and are holding a suspend blocker. + private boolean mUnfinishedBusiness; + + // The elapsed real time when the screen on was blocked. + private long mScreenOnBlockStartRealTime; + private long mScreenOffBlockStartRealTime; + + // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields. + private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED; + + // If the last recorded screen state was dozing or not. + private boolean mDozing; + + // Remembers whether certain kinds of brightness adjustments + // were recently applied so that we can decide how to transition. + private boolean mAppliedAutoBrightness; + private boolean mAppliedDimming; + private boolean mAppliedLowPower; + private boolean mAppliedScreenBrightnessOverride; + private boolean mAppliedTemporaryBrightness; + private boolean mAppliedTemporaryAutoBrightnessAdjustment; + private boolean mAppliedBrightnessBoost; + private boolean mAppliedThrottling; + + // Reason for which the brightness was last changed. See {@link BrightnessReason} for more + // information. + // At the time of this writing, this value is changed within updatePowerState() only, which is + // limited to the thread used by DisplayControllerHandler. + private final BrightnessReason mBrightnessReason = new BrightnessReason(); + private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); + + // Brightness animation ramp rates in brightness units per second + private float mBrightnessRampRateFastDecrease; + private float mBrightnessRampRateFastIncrease; + private float mBrightnessRampRateSlowDecrease; + private float mBrightnessRampRateSlowIncrease; + + // Report HBM brightness change to StatsD + private int mDisplayStatsId; + private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN; + + // Whether or not to skip the initial brightness ramps into STATE_ON. + private final boolean mSkipScreenOnBrightnessRamp; + + // Display white balance components. + @Nullable + private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings; + @Nullable + private final DisplayWhiteBalanceController mDisplayWhiteBalanceController; + + @Nullable + private final ColorDisplayServiceInternal mCdsi; + private float[] mNitsRange; + + private final HighBrightnessModeController mHbmController; + + private final BrightnessThrottler mBrightnessThrottler; + + private final BrightnessSetting mBrightnessSetting; + + private final Runnable mOnBrightnessChangeRunnable; + + private final BrightnessEvent mLastBrightnessEvent; + private final BrightnessEvent mTempBrightnessEvent; + + // Keeps a record of brightness changes for dumpsys. + private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer; + + // A record of state for skipping brightness ramps. + private int mSkipRampState = RAMP_STATE_SKIP_NONE; + + // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL. + private float mInitialAutoBrightness; + + // The controller for the automatic brightness level. + @Nullable + private AutomaticBrightnessController mAutomaticBrightnessController; + + private Sensor mLightSensor; + + // The mappers between ambient lux, display backlight values, and display brightness. + // We will switch between the idle mapper and active mapper in AutomaticBrightnessController. + // Mapper used for active (normal) screen brightness mode + @Nullable + private BrightnessMappingStrategy mInteractiveModeBrightnessMapper; + // Mapper used for idle screen brightness mode + @Nullable + private BrightnessMappingStrategy mIdleModeBrightnessMapper; + + // The current brightness configuration. + @Nullable + private BrightnessConfiguration mBrightnessConfiguration; + + // The last brightness that was set by the user and not temporary. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded. + private float mLastUserSetScreenBrightness = Float.NaN; + + // The screen brightness setting has changed but not taken effect yet. If this is different + // from the current screen brightness setting then this is coming from something other than us + // and should be considered a user interaction. + private float mPendingScreenBrightnessSetting; + + // The last observed screen brightness setting, either set by us or by the settings app on + // behalf of the user. + private float mCurrentScreenBrightnessSetting; + + // The temporary screen brightness. Typically set when a user is interacting with the + // brightness slider but hasn't settled on a choice yet. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set. + private float mTemporaryScreenBrightness; + + // The current screen brightness while in VR mode. + private float mScreenBrightnessForVr; + + // The last auto brightness adjustment that was set by the user and not temporary. Set to + // Float.NaN when an auto-brightness adjustment hasn't been recorded yet. + private float mAutoBrightnessAdjustment; + + // The pending auto brightness adjustment that will take effect on the next power state update. + private float mPendingAutoBrightnessAdjustment; + + // The temporary auto brightness adjustment. Typically set when a user is interacting with the + // adjustment slider but hasn't settled on a choice yet. Set to + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set. + private float mTemporaryAutoBrightnessAdjustment; + + private boolean mIsRbcActive; + + // Whether there's a callback to tell listeners the display has changed scheduled to run. When + // true it implies a wakelock is being held to guarantee the update happens before we collapse + // into suspend and so needs to be cleaned up if the thread is exiting. + // Should only be accessed on the Handler thread. + private boolean mOnStateChangedPending; + + // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many + // suspend blocker acquisitions are pending when shutting down this DPC. + // Should only be accessed on the Handler thread. + private int mOnProximityPositiveMessages; + private int mOnProximityNegativeMessages; + + // Animators. + private ObjectAnimator mColorFadeOnAnimator; + private ObjectAnimator mColorFadeOffAnimator; + private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener; + + // True if this DisplayPowerController2 has been stopped and should no longer be running. + private boolean mStopped; + + private DisplayDeviceConfig mDisplayDeviceConfig; + + // Identifiers for suspend blocker acuisition requests + private final String mSuspendBlockerIdUnfinishedBusiness; + private final String mSuspendBlockerIdOnStateChanged; + private final String mSuspendBlockerIdProxPositive; + private final String mSuspendBlockerIdProxNegative; + private final String mSuspendBlockerIdProxDebounce; + + /** + * Creates the display power controller. + */ + DisplayPowerController2(Context context, Injector injector, + DisplayPowerCallbacks callbacks, Handler handler, + SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, + BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, + Runnable onBrightnessChangeRunnable) { + + mInjector = injector != null ? injector : new Injector(); + mClock = mInjector.getClock(); + mLogicalDisplay = logicalDisplay; + mDisplayId = mLogicalDisplay.getDisplayIdLocked(); + mTag = "DisplayPowerController2[" + mDisplayId + "]"; + mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId); + mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId); + mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId); + mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId); + mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId); + + mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + mDisplayStatsId = mUniqueDisplayId.hashCode(); + mHandler = new DisplayControllerHandler(handler.getLooper()); + mLastBrightnessEvent = new BrightnessEvent(mDisplayId); + mTempBrightnessEvent = new BrightnessEvent(mDisplayId); + + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mBatteryStats = BatteryStatsService.getService(); + } else { + mBatteryStats = null; + } + + mSettingsObserver = new SettingsObserver(mHandler); + mCallbacks = callbacks; + mSensorManager = sensorManager; + mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); + mBlanker = blanker; + mContext = context; + mBrightnessTracker = brightnessTracker; + // TODO: b/186428377 update brightness setting when display changes + mBrightnessSetting = brightnessSetting; + mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; + + PowerManager pm = context.getSystemService(PowerManager.class); + + final Resources resources = context.getResources(); + + // DOZE AND DIM SETTINGS + mScreenBrightnessDozeConfig = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)); + mScreenBrightnessDimConfig = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)); + mScreenBrightnessMinimumDimAmount = resources.getFloat( + R.dimen.config_screenBrightnessMinimumDimAmountFloat); + + + // NORMAL SCREEN SETTINGS + mScreenBrightnessDefault = clampAbsoluteBrightness( + mLogicalDisplay.getDisplayInfoLocked().brightnessDefault); + + // VR SETTINGS + mScreenBrightnessForVrDefault = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR)); + mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR)); + mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness( + pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR)); + + // Check the setting, but also verify that it is the default display. Only the default + // display has an automatic brightness controller running. + // TODO: b/179021925 - Fix to work with multiple displays + mUseSoftwareAutoBrightnessConfig = resources.getBoolean( + R.bool.config_automatic_brightness_available) + && mDisplayId == Display.DEFAULT_DISPLAY; + + mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( + R.bool.config_allowAutoBrightnessWhileDozing); + + mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked() + .getDisplayDeviceConfig(); + + loadBrightnessRampRates(); + mSkipScreenOnBrightnessRamp = resources.getBoolean( + R.bool.config_skipScreenOnBrightnessRamp); + + mHbmController = createHbmControllerLocked(); + + mBrightnessThrottler = createBrightnessThrottlerLocked(); + + // Seed the cached brightness + saveBrightnessInfo(getScreenBrightnessSetting()); + + DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null; + DisplayWhiteBalanceController displayWhiteBalanceController = null; + if (mDisplayId == Display.DEFAULT_DISPLAY) { + try { + displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler); + displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler, + mSensorManager, resources); + displayWhiteBalanceSettings.setCallbacks(this); + displayWhiteBalanceController.setCallbacks(this); + } catch (Exception e) { + Slog.e(mTag, "failed to set up display white-balance: " + e); + } + } + mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings; + mDisplayWhiteBalanceController = displayWhiteBalanceController; + + loadNitsRange(resources); + + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class); + boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() { + @Override + public void onReduceBrightColorsActivationChanged(boolean activated, + boolean userInitiated) { + applyReduceBrightColorsSplineAdjustment(); + + } + + @Override + public void onReduceBrightColorsStrengthChanged(int strength) { + applyReduceBrightColorsSplineAdjustment(); + } + }); + if (active) { + applyReduceBrightColorsSplineAdjustment(); + } + } else { + mCdsi = null; + } + + setUpAutoBrightness(resources, handler); + + mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic(); + mColorFadeFadesConfig = resources.getBoolean( + R.bool.config_animateScreenLights); + + mDisplayBlanksAfterDozeConfig = resources.getBoolean( + R.bool.config_displayBlanksAfterDoze); + + mBrightnessBucketsInDozeConfig = resources.getBoolean( + R.bool.config_displayBrightnessBucketsInDoze); + + loadProximitySensor(); + + mCurrentScreenBrightnessSetting = getScreenBrightnessSetting(); + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + + } + + private void applyReduceBrightColorsSplineAdjustment() { + mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget(); + sendUpdatePowerState(); + } + + private void handleRbcChanged() { + if (mAutomaticBrightnessController == null) { + return; + } + if ((!mAutomaticBrightnessController.isInIdleMode() + && mInteractiveModeBrightnessMapper == null) + || (mAutomaticBrightnessController.isInIdleMode() + && mIdleModeBrightnessMapper == null)) { + Log.w(mTag, "No brightness mapping available to recalculate splines for this mode"); + return; + } + + float[] adjustedNits = new float[mNitsRange.length]; + for (int i = 0; i < mNitsRange.length; i++) { + adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]); + } + mIsRbcActive = mCdsi.isReduceBrightColorsActivated(); + mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits); + } + + /** + * Returns true if the proximity sensor screen-off function is available. + */ + @Override + public boolean isProximitySensorAvailable() { + return mProximitySensor != null; + } + + /** + * Get the {@link BrightnessChangeEvent}s for the specified user. + * + * @param userId userId to fetch data for + * @param includePackage if false will null out the package name in events + */ + @Nullable + @Override + public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents( + @UserIdInt int userId, boolean includePackage) { + if (mBrightnessTracker == null) { + return null; + } + return mBrightnessTracker.getEvents(userId, includePackage); + } + + @Override + public void onSwitchUser(@UserIdInt int newUserId) { + handleSettingsChange(true /* userSwitch */); + if (mBrightnessTracker != null) { + mBrightnessTracker.onSwitchUser(newUserId); + } + } + + @Nullable + @Override + public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( + @UserIdInt int userId) { + if (mBrightnessTracker == null) { + return null; + } + return mBrightnessTracker.getAmbientBrightnessStats(userId); + } + + /** + * Persist the brightness slider events and ambient brightness stats to disk. + */ + @Override + public void persistBrightnessTrackerState() { + if (mBrightnessTracker != null) { + mBrightnessTracker.persistBrightnessTrackerState(); + } + } + + /** + * Requests a new power state. + * The controller makes a copy of the provided object and then + * begins adjusting the power state to match what was requested. + * + * @param request The requested power state. + * @param waitForNegativeProximity If true, issues a request to wait for + * negative proximity before turning the screen back on, + * assuming the screen + * was turned off by the proximity sensor. + * @return True if display is ready, false if there are important changes that must + * be made asynchronously (such as turning the screen on), in which case the caller + * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()} + * then try the request again later until the state converges. + */ + public boolean requestPowerState(DisplayPowerRequest request, + boolean waitForNegativeProximity) { + if (DEBUG) { + Slog.d(mTag, "requestPowerState: " + + request + ", waitForNegativeProximity=" + waitForNegativeProximity); + } + + synchronized (mLock) { + if (mStopped) { + return true; + } + + boolean changed = false; + + if (waitForNegativeProximity + && !mPendingWaitForNegativeProximityLocked) { + mPendingWaitForNegativeProximityLocked = true; + changed = true; + } + + if (mPendingRequestLocked == null) { + mPendingRequestLocked = new DisplayPowerRequest(request); + changed = true; + } else if (!mPendingRequestLocked.equals(request)) { + mPendingRequestLocked.copyFrom(request); + changed = true; + } + + if (changed) { + mDisplayReadyLocked = false; + if (!mPendingRequestChangedLocked) { + mPendingRequestChangedLocked = true; + sendUpdatePowerStateLocked(); + } + } + + return mDisplayReadyLocked; + } + } + + @Override + public BrightnessConfiguration getDefaultBrightnessConfiguration() { + if (mAutomaticBrightnessController == null) { + return null; + } + return mAutomaticBrightnessController.getDefaultConfig(); + } + + /** + * Notified when the display is changed. We use this to apply any changes that might be needed + * when displays get swapped on foldable devices. For example, different brightness properties + * of each display need to be properly reflected in AutomaticBrightnessController. + * + * Make sure DisplayManagerService.mSyncRoot lock is held when this is called + */ + @Override + public void onDisplayChanged() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: " + + mLogicalDisplay.getDisplayIdLocked()); + return; + } + + final String uniqueId = device.getUniqueId(); + final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); + final IBinder token = device.getDisplayTokenLocked(); + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + mHandler.post(() -> { + if (mDisplayDevice != device) { + mDisplayDevice = device; + mUniqueDisplayId = uniqueId; + mDisplayStatsId = mUniqueDisplayId.hashCode(); + mDisplayDeviceConfig = config; + loadFromDisplayDeviceConfig(token, info); + updatePowerState(); + } + }); + } + + /** + * Called when the displays are preparing to transition from one device state to another. + * This process involves turning off some displays so we need updatePowerState() to run and + * calculate the new state. + */ + @Override + public void onDeviceStateTransition() { + sendUpdatePowerState(); + } + + /** + * Unregisters all listeners and interrupts all running threads; halting future work. + * + * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when + * the {@link #mDisplayId display} has been removed. + */ + @Override + public void stop() { + synchronized (mLock) { + mStopped = true; + Message msg = mHandler.obtainMessage(MSG_STOP); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setEnabled(false); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.stop(); + } + + if (mBrightnessSetting != null) { + mBrightnessSetting.unregisterListener(mBrightnessSettingListener); + } + + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + } + + private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) { + // All properties that depend on the associated DisplayDevice and the DDC must be + // updated here. + loadBrightnessRampRates(); + loadProximitySensor(); + loadNitsRange(mContext.getResources()); + setUpAutoBrightness(mContext.getResources(), mHandler); + reloadReduceBrightColours(); + if (mScreenBrightnessRampAnimator != null) { + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); + } + mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, + mDisplayDeviceConfig.getHighBrightnessModeData(), + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }); + mBrightnessThrottler.resetThrottlingData( + mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId); + } + + private void sendUpdatePowerState() { + synchronized (mLock) { + sendUpdatePowerStateLocked(); + } + } + + @GuardedBy("mLock") + private void sendUpdatePowerStateLocked() { + if (!mStopped && !mPendingUpdatePowerStateLocked) { + mPendingUpdatePowerStateLocked = true; + Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + } + } + + private void initialize(int displayState) { + mPowerState = mInjector.getDisplayPowerState(mBlanker, + mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState); + + if (mColorFadeEnabled) { + mColorFadeOnAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f); + mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS); + mColorFadeOnAnimator.addListener(mAnimatorListener); + + mColorFadeOffAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f); + mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS); + mColorFadeOffAnimator.addListener(mAnimatorListener); + } + + mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState, + DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, + DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); + mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); + + noteScreenState(mPowerState.getScreenState()); + noteScreenBrightness(mPowerState.getScreenBrightness()); + + // Initialize all of the brightness tracking state + final float brightness = convertToNits(mPowerState.getScreenBrightness()); + if (brightness >= PowerManager.BRIGHTNESS_MIN) { + mBrightnessTracker.start(brightness); + } + mBrightnessSettingListener = brightnessValue -> { + Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue); + mHandler.sendMessage(msg); + }; + + mBrightnessSetting.registerListener(mBrightnessSettingListener); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + } + + private void setUpAutoBrightness(Resources resources, Handler handler) { + if (!mUseSoftwareAutoBrightnessConfig) { + return; + } + + final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( + R.bool.config_enableIdleScreenBrightnessMode); + mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources, + mDisplayDeviceConfig, mDisplayWhiteBalanceController); + if (isIdleScreenBrightnessEnabled) { + mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources, + mDisplayDeviceConfig, mDisplayWhiteBalanceController); + } + + if (mInteractiveModeBrightnessMapper != null) { + final float dozeScaleFactor = resources.getFraction( + R.fraction.config_screenAutoBrightnessDozeScaleFactor, + 1, 1); + + int[] ambientBrighteningThresholds = resources.getIntArray( + R.array.config_ambientBrighteningThresholds); + int[] ambientDarkeningThresholds = resources.getIntArray( + R.array.config_ambientDarkeningThresholds); + int[] ambientThresholdLevels = resources.getIntArray( + R.array.config_ambientThresholdLevels); + float ambientDarkeningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(); + float ambientBrighteningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(); + HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientThresholdLevels, ambientDarkeningMinThreshold, + ambientBrighteningMinThreshold); + + int[] screenBrighteningThresholds = resources.getIntArray( + R.array.config_screenBrighteningThresholds); + int[] screenDarkeningThresholds = resources.getIntArray( + R.array.config_screenDarkeningThresholds); + int[] screenThresholdLevels = resources.getIntArray( + R.array.config_screenThresholdLevels); + float screenDarkeningMinThreshold = + mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); + float screenBrighteningMinThreshold = + mDisplayDeviceConfig.getScreenBrighteningMinThreshold(); + HysteresisLevels screenBrightnessThresholds = new HysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, + screenDarkeningMinThreshold, screenBrighteningMinThreshold); + + // Idle screen thresholds + float screenDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); + float screenBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); + HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels, + screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + + // Idle ambient thresholds + float ambientDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); + float ambientBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); + HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientThresholdLevels, ambientDarkeningMinThresholdIdle, + ambientBrighteningMinThresholdIdle); + + long brighteningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounce(); + long darkeningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounce(); + boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( + R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); + + int lightSensorWarmUpTimeConfig = resources.getInteger( + R.integer.config_lightSensorWarmupTime); + int lightSensorRate = resources.getInteger( + R.integer.config_autoBrightnessLightSensorRate); + int initialLightSensorRate = resources.getInteger( + R.integer.config_autoBrightnessInitialLightSensorRate); + if (initialLightSensorRate == -1) { + initialLightSensorRate = lightSensorRate; + } else if (initialLightSensorRate > lightSensorRate) { + Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate (" + + initialLightSensorRate + ") to be less than or equal to " + + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ")."); + } + + loadAmbientLightSensor(); + if (mBrightnessTracker != null) { + mBrightnessTracker.setLightSensor(mLightSensor); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.stop(); + } + mAutomaticBrightnessController = new AutomaticBrightnessController(this, + handler.getLooper(), mSensorManager, mLightSensor, + mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, + lightSensorRate, initialLightSensorRate, brighteningLightDebounce, + darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + ambientBrightnessThresholds, screenBrightnessThresholds, + ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, + mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, + mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong()); + + mBrightnessEventRingBuffer = + new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX); + } else { + mUseSoftwareAutoBrightnessConfig = false; + } + } + + private void loadBrightnessRampRates() { + mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease(); + mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease(); + mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease(); + mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease(); + mBrightnessRampDecreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(); + mBrightnessRampIncreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(); + } + + private void loadNitsRange(Resources resources) { + if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) { + mNitsRange = mDisplayDeviceConfig.getNits(); + } else { + Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back"); + mNitsRange = BrightnessMappingStrategy.getFloatArray(resources + .obtainTypedArray(R.array.config_screenBrightnessNits)); + } + } + + private void reloadReduceBrightColours() { + if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) { + applyReduceBrightColorsSplineAdjustment(); + } + } + + @Override + public void setAutomaticScreenBrightnessMode(boolean isIdle) { + if (mAutomaticBrightnessController != null) { + if (isIdle) { + mAutomaticBrightnessController.switchToIdleMode(); + } else { + mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode(); + } + } + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); + } + } + + private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + sendUpdatePowerState(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public void onAnimationCancel(Animator animation) { + } + }; + + private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() { + @Override + public void onAnimationEnd() { + sendUpdatePowerState(); + Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE); + mHandler.sendMessage(msg); + } + }; + + /** Clean up all resources that are accessed via the {@link #mHandler} thread. */ + private void cleanupHandlerThreadAfterStop() { + setProximitySensorEnabled(false); + mHbmController.stop(); + mBrightnessThrottler.stop(); + mHandler.removeCallbacksAndMessages(null); + + // Release any outstanding wakelocks we're still holding because of pending messages. + if (mUnfinishedBusiness) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + mUnfinishedBusiness = false; + } + if (mOnStateChangedPending) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mOnStateChangedPending = false; + } + for (int i = 0; i < mOnProximityPositiveMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); + } + mOnProximityPositiveMessages = 0; + for (int i = 0; i < mOnProximityNegativeMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); + } + mOnProximityNegativeMessages = 0; + + final float brightness = mPowerState != null + ? mPowerState.getScreenBrightness() + : PowerManager.BRIGHTNESS_MIN; + reportStats(brightness); + + if (mPowerState != null) { + mPowerState.stop(); + mPowerState = null; + } + } + + private void updatePowerState() { + if (DEBUG) { + Trace.beginSection("DisplayPowerController#updatePowerState"); + } + updatePowerStateInternal(); + if (DEBUG) { + Trace.endSection(); + } + } + + private void updatePowerStateInternal() { + // Update the power state request. + final boolean mustNotify; + final int previousPolicy; + boolean mustInitialize = false; + int brightnessAdjustmentFlags = 0; + mBrightnessReasonTemp.set(null); + mTempBrightnessEvent.reset(); + synchronized (mLock) { + if (mStopped) { + return; + } + mPendingUpdatePowerStateLocked = false; + if (mPendingRequestLocked == null) { + return; // wait until first actual power request + } + + if (mPowerRequest == null) { + mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked); + updatePendingProximityRequestsLocked(); + mPendingRequestChangedLocked = false; + mustInitialize = true; + // Assume we're on and bright until told otherwise, since that's the state we turn + // on in. + previousPolicy = DisplayPowerRequest.POLICY_BRIGHT; + } else if (mPendingRequestChangedLocked) { + previousPolicy = mPowerRequest.policy; + mPowerRequest.copyFrom(mPendingRequestLocked); + updatePendingProximityRequestsLocked(); + mPendingRequestChangedLocked = false; + mDisplayReadyLocked = false; + } else { + previousPolicy = mPowerRequest.policy; + } + + mustNotify = !mDisplayReadyLocked; + } + + // Compute the basic display state using the policy. + // We might override this below based on other factors. + // Initialise brightness as invalid. + int state; + float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; + boolean performScreenOffTransition = false; + switch (mPowerRequest.policy) { + case DisplayPowerRequest.POLICY_OFF: + state = Display.STATE_OFF; + performScreenOffTransition = true; + break; + case DisplayPowerRequest.POLICY_DOZE: + if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { + state = mPowerRequest.dozeScreenState; + } else { + state = Display.STATE_DOZE; + } + if (!mAllowAutoBrightnessWhileDozingConfig) { + brightnessState = mPowerRequest.dozeScreenBrightness; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); + } + break; + case DisplayPowerRequest.POLICY_VR: + state = Display.STATE_VR; + break; + case DisplayPowerRequest.POLICY_DIM: + case DisplayPowerRequest.POLICY_BRIGHT: + default: + state = Display.STATE_ON; + break; + } + assert (state != Display.STATE_UNKNOWN); + + boolean skipRampBecauseOfProximityChangeToNegative = false; + // Apply the proximity sensor. + if (mProximitySensor != null) { + if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { + // At this point the policy says that the screen should be on, but we've been + // asked to listen to the prox sensor to adjust the display state, so lets make + // sure the sensor is on. + setProximitySensorEnabled(true); + if (!mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && !mIgnoreProximityUntilChanged) { + // Prox sensor already reporting "near" so we should turn off the screen. + // Also checked that we aren't currently set to ignore the proximity sensor + // temporarily. + mScreenOffBecauseOfProximity = true; + sendOnProximityPositiveWithWakelock(); + } + } else if (mWaitingForNegativeProximity + && mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && state != Display.STATE_OFF) { + // The policy says that we should have the screen on, but it's off due to the prox + // and we've been asked to wait until the screen is far from the user to turn it + // back on. Let keep the prox sensor on so we can tell when it's far again. + setProximitySensorEnabled(true); + } else { + // We haven't been asked to use the prox sensor and we're not waiting on the screen + // to turn back on...so lets shut down the prox sensor. + setProximitySensorEnabled(false); + mWaitingForNegativeProximity = false; + } + + if (mScreenOffBecauseOfProximity + && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) { + // The screen *was* off due to prox being near, but now it's "far" so lets turn + // the screen back on. Also turn it back on if we've been asked to ignore the + // prox sensor temporarily. + mScreenOffBecauseOfProximity = false; + skipRampBecauseOfProximityChangeToNegative = true; + sendOnProximityNegativeWithWakelock(); + } + } else { + mWaitingForNegativeProximity = false; + mIgnoreProximityUntilChanged = false; + } + + if (!mLogicalDisplay.isEnabled() + || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + || mScreenOffBecauseOfProximity) { + state = Display.STATE_OFF; + } + + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(state); + } + + // Animate the screen state change unless already animating. + // The transition may be deferred, so after this point we will use the + // actual state instead of the desired one. + final int oldState = mPowerState.getScreenState(); + animateScreenStateChange(state, performScreenOffTransition); + state = mPowerState.getScreenState(); + + if (state == Display.STATE_OFF) { + brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF); + } + + // Always use the VR brightness when in the VR state. + if (state == Display.STATE_VR) { + brightnessState = mScreenBrightnessForVr; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR); + } + + if ((Float.isNaN(brightnessState)) + && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) { + brightnessState = mPowerRequest.screenBrightnessOverride; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE); + mAppliedScreenBrightnessOverride = true; + } else { + mAppliedScreenBrightnessOverride = false; + } + + final boolean autoBrightnessEnabledInDoze = + mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); + final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness + && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) + && Float.isNaN(brightnessState) + && mAutomaticBrightnessController != null; + final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness + && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze); + final int autoBrightnessState = autoBrightnessEnabled + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED + : autoBrightnessDisabledDueToDisplayOff + ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE + : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; + + final boolean userSetBrightnessChanged = updateUserSetScreenBrightness(); + + // Use the temporary screen brightness if there isn't an override, either from + // WindowManager or based on the display state. + if (isValidBrightnessValue(mTemporaryScreenBrightness)) { + brightnessState = mTemporaryScreenBrightness; + mAppliedTemporaryBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY); + } else { + mAppliedTemporaryBrightness = false; + } + + final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment(); + + // Use the autobrightness adjustment override if set. + final float autoBrightnessAdjustment; + if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) { + autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP; + mAppliedTemporaryAutoBrightnessAdjustment = true; + } else { + autoBrightnessAdjustment = mAutoBrightnessAdjustment; + brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO; + mAppliedTemporaryAutoBrightnessAdjustment = false; + } + // Apply brightness boost. + // We do this here after deciding whether auto-brightness is enabled so that we don't + // disable the light sensor during this temporary state. That way when boost ends we will + // be able to resume normal auto-brightness behavior without any delay. + if (mPowerRequest.boostScreenBrightness + && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) { + brightnessState = PowerManager.BRIGHTNESS_MAX; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST); + mAppliedBrightnessBoost = true; + } else { + mAppliedBrightnessBoost = false; + } + + // If the brightness is already set then it's been overridden by something other than the + // user, or is a temporary adjustment. + boolean userInitiatedChange = (Float.isNaN(brightnessState)) + && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); + boolean hadUserBrightnessPoint = false; + // Configure auto-brightness. + if (mAutomaticBrightnessController != null) { + hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); + mAutomaticBrightnessController.configure(autoBrightnessState, + mBrightnessConfiguration, + mLastUserSetScreenBrightness, + userSetBrightnessChanged, autoBrightnessAdjustment, + autoBrightnessAdjustmentChanged, mPowerRequest.policy); + } + + if (mBrightnessTracker != null) { + mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration); + } + + boolean updateScreenBrightnessSetting = false; + + // Apply auto-brightness. + boolean slowChange = false; + if (Float.isNaN(brightnessState)) { + float newAutoBrightnessAdjustment = autoBrightnessAdjustment; + if (autoBrightnessEnabled) { + brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness( + mTempBrightnessEvent); + newAutoBrightnessAdjustment = + mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment(); + } + if (isValidBrightnessValue(brightnessState) + || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) { + // Use current auto-brightness value and slowly adjust to changes. + brightnessState = clampScreenBrightness(brightnessState); + if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) { + slowChange = true; // slowly adapt to auto-brightness + } + updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState; + mAppliedAutoBrightness = true; + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC); + } else { + mAppliedAutoBrightness = false; + } + if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) { + // If the autobrightness controller has decided to change the adjustment value + // used, make sure that's reflected in settings. + putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment); + } else { + // Adjustment values resulted in no change + brightnessAdjustmentFlags = 0; + } + } else { + // Any non-auto-brightness values such as override or temporary should still be subject + // to clamping so that they don't go beyond the current max as specified by HBM + // Controller. + brightnessState = clampScreenBrightness(brightnessState); + mAppliedAutoBrightness = false; + brightnessAdjustmentFlags = 0; + } + + // Use default brightness when dozing unless overridden. + if ((Float.isNaN(brightnessState)) + && Display.isDozeState(state)) { + brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); + } + + // Apply manual brightness. + if (Float.isNaN(brightnessState)) { + brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting); + if (brightnessState != mCurrentScreenBrightnessSetting) { + // The manually chosen screen brightness is outside of the currently allowed + // range (i.e., high-brightness-mode), make sure we tell the rest of the system + // by updating the setting. + updateScreenBrightnessSetting = true; + } + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); + } + + // Now that a desired brightness has been calculated, apply brightness throttling. The + // dimming and low power transformations that follow can only dim brightness further. + // + // We didn't do this earlier through brightness clamping because we need to know both + // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations. + // Note throttling effectively changes the allowed brightness range, so, similarly to HBM, + // we broadcast this change through setting. + final float unthrottledBrightnessState = brightnessState; + if (mBrightnessThrottler.isThrottled()) { + mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap()); + brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap()); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED); + if (!mAppliedThrottling) { + // Brightness throttling is needed, so do so quickly. + // Later, when throttling is removed, we let other mechanisms decide on speed. + slowChange = false; + } + mAppliedThrottling = true; + } else if (mAppliedThrottling) { + mAppliedThrottling = false; + } + + if (updateScreenBrightnessSetting) { + // Tell the rest of the system about the new brightness in case we had to change it + // for things like auto-brightness or high-brightness-mode. Note that we do this + // before applying the low power or dim transformations so that the slider + // accurately represents the full possible range, even if they range changes what + // it means in absolute terms. + updateScreenBrightnessSetting(brightnessState); + } + + // Apply dimming by at least some minimum amount when user activity + // timeout is about to expire. + if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { + brightnessState = Math.max( + Math.min(brightnessState - mScreenBrightnessMinimumDimAmount, + mScreenBrightnessDimConfig), + PowerManager.BRIGHTNESS_MIN); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); + } + if (!mAppliedDimming) { + slowChange = false; + } + mAppliedDimming = true; + } else if (mAppliedDimming) { + slowChange = false; + mAppliedDimming = false; + } + // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor + // as long as it is above the minimum threshold. + if (mPowerRequest.lowPowerMode) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { + final float brightnessFactor = + Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); + final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor); + brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER); + } + if (!mAppliedLowPower) { + slowChange = false; + } + mAppliedLowPower = true; + } else if (mAppliedLowPower) { + slowChange = false; + mAppliedLowPower = false; + } + + // The current brightness to use has been calculated at this point, and HbmController should + // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it + // here instead of having HbmController listen to the brightness setting because certain + // brightness sources (such as an app override) are not saved to the setting, but should be + // reflected in HBM calculations. + mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, + mBrightnessThrottler.getBrightnessMaxReason()); + + // Animate the screen brightness when the screen is on or dozing. + // Skip the animation when the screen is off or suspended or transition to/from VR. + boolean brightnessAdjusted = false; + final boolean brightnessIsTemporary = + mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment; + if (!mPendingScreenOff) { + if (mSkipScreenOnBrightnessRamp) { + if (state == Display.STATE_ON) { + if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) { + mInitialAutoBrightness = brightnessState; + mSkipRampState = RAMP_STATE_SKIP_INITIAL; + } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL + && mUseSoftwareAutoBrightnessConfig + && !BrightnessSynchronizer.floatEquals(brightnessState, + mInitialAutoBrightness)) { + mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT; + } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) { + mSkipRampState = RAMP_STATE_SKIP_NONE; + } + } else { + mSkipRampState = RAMP_STATE_SKIP_NONE; + } + } + + final boolean wasOrWillBeInVr = + (state == Display.STATE_VR || oldState == Display.STATE_VR); + final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState + != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative; + // While dozing, sometimes the brightness is split into buckets. Rather than animating + // through the buckets, which is unlikely to be smooth in the first place, just jump + // right to the suggested brightness. + final boolean hasBrightnessBuckets = + Display.isDozeState(state) && mBrightnessBucketsInDozeConfig; + // If the color fade is totally covering the screen then we can change the backlight + // level without it being a noticeable jump since any actual content isn't yet visible. + final boolean isDisplayContentVisible = + mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f; + // We only want to animate the brightness if it is between 0.0f and 1.0f. + // brightnessState can contain the values -1.0f and NaN, which we do not want to + // animate to. To avoid this, we check the value first. + // If the brightnessState is off (-1.0f) we still want to animate to the minimum + // brightness (0.0f) to accommodate for LED displays, which can appear bright to the + // user even when the display is all black. We also clamp here in case some + // transformations to the brightness have pushed it outside of the currently + // allowed range. + float animateValue = clampScreenBrightness(brightnessState); + + // If there are any HDR layers on the screen, we have a special brightness value that we + // use instead. We still preserve the calculated brightness for Standard Dynamic Range + // (SDR) layers, but the main brightness value will be the one for HDR. + float sdrAnimateValue = animateValue; + // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be + // done in HighBrightnessModeController. + if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 + && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) + == 0) { + // We want to scale HDR brightness level with the SDR level + animateValue = mHbmController.getHdrBrightnessValue(); + } + + final float currentBrightness = mPowerState.getScreenBrightness(); + final float currentSdrBrightness = mPowerState.getSdrScreenBrightness(); + if (isValidBrightnessValue(animateValue) + && (animateValue != currentBrightness + || sdrAnimateValue != currentSdrBrightness)) { + if (initialRampSkip || hasBrightnessBuckets + || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { + animateScreenBrightness(animateValue, sdrAnimateValue, + SCREEN_ANIMATION_RATE_MINIMUM); + } else { + boolean isIncreasing = animateValue > currentBrightness; + final float rampSpeed; + if (isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowIncrease; + } else if (isIncreasing && !slowChange) { + rampSpeed = mBrightnessRampRateFastIncrease; + } else if (!isIncreasing && slowChange) { + rampSpeed = mBrightnessRampRateSlowDecrease; + } else { + rampSpeed = mBrightnessRampRateFastDecrease; + } + animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed); + } + } + + // Report brightness to brightnesstracker: + // If brightness is not temporary (ie the slider has been released) + // AND if we are not in idle screen brightness mode. + if (!brightnessIsTemporary + && (mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode())) { + if (userInitiatedChange && (mAutomaticBrightnessController == null + || !mAutomaticBrightnessController.hasValidAmbientLux())) { + // If we don't have a valid lux reading we can't report a valid + // slider event so notify as if the system changed the brightness. + userInitiatedChange = false; + } + notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, + hadUserBrightnessPoint); + } + + // We save the brightness info *after* the brightness setting has been changed and + // adjustments made so that the brightness info reflects the latest value. + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); + } else { + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting()); + } + + // Only notify if the brightness adjustment is not temporary (i.e. slider has been released) + if (brightnessAdjusted && !brightnessIsTemporary) { + postBrightnessChangeRunnable(); + } + + // Log any changes to what is currently driving the brightness setting. + if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) { + Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '" + + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags) + + "', previous reason: '" + mBrightnessReason + "'."); + mBrightnessReason.set(mBrightnessReasonTemp); + } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL + && userSetBrightnessChanged) { + Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment."); + } + + + // Log brightness events when a detail of significance has changed. Generally this is the + // brightness itself changing, but also includes data like HBM cap, thermal throttling + // brightness cap, RBC state, etc. + mTempBrightnessEvent.setTime(System.currentTimeMillis()); + mTempBrightnessEvent.setBrightness(brightnessState); + mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); + mTempBrightnessEvent.setReason(mBrightnessReason); + mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax()); + mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode()); + mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() + | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) + | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)); + mTempBrightnessEvent.setRbcStrength(mCdsi != null + ? mCdsi.getReduceBrightColorsStrength() : -1); + mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); + // Temporary is what we use during slider interactions. We avoid logging those so that + // we don't spam logcat when the slider is being used. + boolean tempToTempTransition = + mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY + && mLastBrightnessEvent.getReason().getReason() + == BrightnessReason.REASON_TEMPORARY; + if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition) + || brightnessAdjustmentFlags != 0) { + float lastBrightness = mLastBrightnessEvent.getBrightness(); + mTempBrightnessEvent.setInitialBrightness(lastBrightness); + mTempBrightnessEvent.setFastAmbientLux( + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getFastAmbientLux()); + mTempBrightnessEvent.setSlowAmbientLux( + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getSlowAmbientLux()); + mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness); + mLastBrightnessEvent.copyFrom(mTempBrightnessEvent); + BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent); + // Adjustment flags (and user-set flag) only get added after the equality checks since + // they are transient. + newEvent.setAdjustmentFlags(brightnessAdjustmentFlags); + newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged + ? BrightnessEvent.FLAG_USER_SET : 0)); + Slog.i(mTag, newEvent.toString(/* includeTime= */ false)); + + if (userSetBrightnessChanged) { + logManualBrightnessEvent(newEvent); + } + if (mBrightnessEventRingBuffer != null) { + mBrightnessEventRingBuffer.append(newEvent); + } + } + + // Update display white-balance. + if (mDisplayWhiteBalanceController != null) { + if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) { + mDisplayWhiteBalanceController.setEnabled(true); + mDisplayWhiteBalanceController.updateDisplayColorTemperature(); + } else { + mDisplayWhiteBalanceController.setEnabled(false); + } + } + + // Determine whether the display is ready for use in the newly requested state. + // Note that we do not wait for the brightness ramp animation to complete before + // reporting the display is ready because we only need to ensure the screen is in the + // right power state even as it continues to converge on the desired brightness. + final boolean ready = mPendingScreenOnUnblocker == null + && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted() + && !mColorFadeOffAnimator.isStarted())) + && mPowerState.waitUntilClean(mCleanListener); + final boolean finished = ready + && !mScreenBrightnessRampAnimator.isAnimating(); + + // Notify policy about screen turned on. + if (ready && state != Display.STATE_OFF + && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON); + mWindowManagerPolicy.screenTurnedOn(mDisplayId); + } + + // Grab a wake lock if we have unfinished business. + if (!finished && !mUnfinishedBusiness) { + if (DEBUG) { + Slog.d(mTag, "Unfinished business..."); + } + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + mUnfinishedBusiness = true; + } + + // Notify the power manager when ready. + if (ready && mustNotify) { + // Send state change. + synchronized (mLock) { + if (!mPendingRequestChangedLocked) { + mDisplayReadyLocked = true; + + if (DEBUG) { + Slog.d(mTag, "Display ready!"); + } + } + } + sendOnStateChangedWithWakelock(); + } + + // Release the wake lock when we have no unfinished business. + if (finished && mUnfinishedBusiness) { + if (DEBUG) { + Slog.d(mTag, "Finished business..."); + } + mUnfinishedBusiness = false; + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); + } + + // Record if dozing for future comparison. + mDozing = state != Display.STATE_ON; + + if (previousPolicy != mPowerRequest.policy) { + logDisplayPolicyChanged(mPowerRequest.policy); + } + } + + @Override + public void updateBrightness() { + sendUpdatePowerState(); + } + + /** + * Ignores the proximity sensor until the sensor state changes, but only if the sensor is + * currently enabled and forcing the screen to be dark. + */ + @Override + public void ignoreProximitySensorUntilChanged() { + mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY); + } + + @Override + public void setBrightnessConfiguration(BrightnessConfiguration c) { + Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c); + msg.sendToTarget(); + } + + @Override + public void setTemporaryBrightness(float brightness) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS, + Float.floatToIntBits(brightness), 0 /*unused*/); + msg.sendToTarget(); + } + + @Override + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT, + Float.floatToIntBits(adjustment), 0 /*unused*/); + msg.sendToTarget(); + } + + @Override + public BrightnessInfo getBrightnessInfo() { + synchronized (mCachedBrightnessInfo) { + return new BrightnessInfo( + mCachedBrightnessInfo.brightness.value, + mCachedBrightnessInfo.adjustedBrightness.value, + mCachedBrightnessInfo.brightnessMin.value, + mCachedBrightnessInfo.brightnessMax.value, + mCachedBrightnessInfo.hbmMode.value, + mCachedBrightnessInfo.hbmTransitionPoint.value, + mCachedBrightnessInfo.brightnessMaxReason.value); + } + } + + private boolean saveBrightnessInfo(float brightness) { + return saveBrightnessInfo(brightness, brightness); + } + + private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { + synchronized (mCachedBrightnessInfo) { + final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + mBrightnessThrottler.getBrightnessCap()); + final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + mBrightnessThrottler.getBrightnessCap()); + boolean changed = false; + + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness, + brightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness, + adjustedBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, + minBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, + maxBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, + mHbmController.getHighBrightnessMode()); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, + mHbmController.getTransitionPoint()); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, + mBrightnessThrottler.getBrightnessMaxReason()); + + return changed; + } + } + + void postBrightnessChangeRunnable() { + mHandler.post(mOnBrightnessChangeRunnable); + } + + private HighBrightnessModeController createHbmControllerLocked() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); + final IBinder displayToken = + mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked(); + final String displayUniqueId = + mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + final DisplayDeviceConfig.HighBrightnessModeData hbmData = + ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, + displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }, + () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } + }, mContext); + } + + private BrightnessThrottler createBrightnessThrottlerLocked() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); + final DisplayDeviceConfig.BrightnessThrottlingData data = + ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null; + return new BrightnessThrottler(mHandler, data, + () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + }, mUniqueDisplayId); + } + + private void blockScreenOn() { + if (mPendingScreenOnUnblocker == null) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); + mPendingScreenOnUnblocker = new ScreenOnUnblocker(); + mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime(); + Slog.i(mTag, "Blocking screen on until initial contents have been drawn."); + } + } + + private void unblockScreenOn() { + if (mPendingScreenOnUnblocker != null) { + mPendingScreenOnUnblocker = null; + long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime; + Slog.i(mTag, "Unblocked screen on after " + delay + " ms"); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); + } + } + + private void blockScreenOff() { + if (mPendingScreenOffUnblocker == null) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0); + mPendingScreenOffUnblocker = new ScreenOffUnblocker(); + mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime(); + Slog.i(mTag, "Blocking screen off"); + } + } + + private void unblockScreenOff() { + if (mPendingScreenOffUnblocker != null) { + mPendingScreenOffUnblocker = null; + long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime; + Slog.i(mTag, "Unblocked screen off after " + delay + " ms"); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0); + } + } + + private boolean setScreenState(int state) { + return setScreenState(state, false /*reportOnly*/); + } + + private boolean setScreenState(int state, boolean reportOnly) { + final boolean isOff = (state == Display.STATE_OFF); + + if (mPowerState.getScreenState() != state + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) { + // If we are trying to turn screen off, give policy a chance to do something before we + // actually turn the screen off. + if (isOff && !mScreenOffBecauseOfProximity) { + if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF); + blockScreenOff(); + mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker); + unblockScreenOff(); + } else if (mPendingScreenOffUnblocker != null) { + // Abort doing the state change until screen off is unblocked. + return false; + } + } + + if (!reportOnly && mPowerState.getScreenState() != state) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state); + // TODO(b/153319140) remove when we can get this from the above trace invocation + SystemProperties.set("debug.tracing.screen_state", String.valueOf(state)); + mPowerState.setScreenState(state); + // Tell battery stats about the transition. + noteScreenState(state); + } + } + + // Tell the window manager policy when the screen is turned off or on unless it's due + // to the proximity sensor. We temporarily block turning the screen on until the + // window manager is ready by leaving a black surface covering the screen. + // This surface is essentially the final state of the color fade animation and + // it is only removed once the window manager tells us that the activity has + // finished drawing underneath. + if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF + && !mScreenOffBecauseOfProximity) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); + unblockScreenOn(); + mWindowManagerPolicy.screenTurnedOff(mDisplayId); + } else if (!isOff + && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) { + + // We told policy already that screen was turning off, but now we changed our minds. + // Complete the full state transition on -> turningOff -> off. + unblockScreenOff(); + mWindowManagerPolicy.screenTurnedOff(mDisplayId); + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); + } + if (!isOff + && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF + || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) { + setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON); + if (mPowerState.getColorFadeLevel() == 0.0f) { + blockScreenOn(); + } else { + unblockScreenOn(); + } + mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker); + } + + // Return true if the screen isn't blocked. + return mPendingScreenOnUnblocker == null; + } + + private void setReportedScreenState(int state) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state); + mReportedScreenStateToPolicy = state; + } + + private void loadAmbientLightSensor() { + DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor(); + final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY + ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK; + mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name, + fallbackType); + } + + private void loadProximitySensor() { + if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) { + return; + } + final DisplayDeviceConfig.SensorData proxSensor = + mDisplayDeviceConfig.getProximitySensor(); + final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY + ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK; + mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name, + fallbackType); + if (mProximitySensor != null) { + mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(), + TYPICAL_PROXIMITY_THRESHOLD); + } + } + + private float clampScreenBrightnessForVr(float value) { + return MathUtils.constrain( + value, mScreenBrightnessForVrRangeMinimum, + mScreenBrightnessForVrRangeMaximum); + } + + private float clampScreenBrightness(float value) { + if (Float.isNaN(value)) { + value = PowerManager.BRIGHTNESS_MIN; + } + return MathUtils.constrain(value, + mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); + } + + // Checks whether the brightness is within the valid brightness range, not including off. + private boolean isValidBrightnessValue(float brightness) { + return brightness >= PowerManager.BRIGHTNESS_MIN + && brightness <= PowerManager.BRIGHTNESS_MAX; + } + + private void animateScreenBrightness(float target, float sdrTarget, float rate) { + if (DEBUG) { + Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget + + ", rate=" + rate); + } + if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target); + // TODO(b/153319140) remove when we can get this from the above trace invocation + SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target)); + noteScreenBrightness(target); + } + } + + private void animateScreenStateChange(int target, boolean performScreenOffTransition) { + // If there is already an animation in progress, don't interfere with it. + if (mColorFadeEnabled + && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) { + if (target != Display.STATE_ON) { + return; + } + // If display state changed to on, proceed and stop the color fade and turn screen on. + mPendingScreenOff = false; + } + + if (mDisplayBlanksAfterDozeConfig + && Display.isDozeState(mPowerState.getScreenState()) + && !Display.isDozeState(target)) { + // Skip the screen off animation and add a black surface to hide the + // contents of the screen. + mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP); + if (mColorFadeOffAnimator != null) { + mColorFadeOffAnimator.end(); + } + // Some display hardware will blank itself on the transition between doze and non-doze + // but still on display states. In this case we want to report to policy that the + // display has turned off so it can prepare the appropriate power on animation, but we + // don't want to actually transition to the fully off state since that takes + // significantly longer to transition from. + setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/); + } + + // If we were in the process of turning off the screen but didn't quite + // finish. Then finish up now to prevent a jarring transition back + // to screen on if we skipped blocking screen on as usual. + if (mPendingScreenOff && target != Display.STATE_OFF) { + setScreenState(Display.STATE_OFF); + mPendingScreenOff = false; + mPowerState.dismissColorFadeResources(); + } + + if (target == Display.STATE_ON) { + // Want screen on. The contents of the screen may not yet + // be visible if the color fade has not been dismissed because + // its last frame of animation is solid black. + if (!setScreenState(Display.STATE_ON)) { + return; // screen on blocked + } + if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) { + // Perform screen on animation. + if (mPowerState.getColorFadeLevel() == 1.0f) { + mPowerState.dismissColorFade(); + } else if (mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig + ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) { + mColorFadeOnAnimator.start(); + } else { + mColorFadeOnAnimator.end(); + } + } else { + // Skip screen on animation. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } + } else if (target == Display.STATE_VR) { + // Wait for brightness animation to complete beforehand when entering VR + // from screen on to prevent a perceptible jump because brightness may operate + // differently when the display is configured for dozing. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() == Display.STATE_ON) { + return; + } + + // Set screen state. + if (!setScreenState(Display.STATE_VR)) { + return; // screen on blocked + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_DOZE) { + // Want screen dozing. + // Wait for brightness animation to complete beforehand when entering doze + // from screen on to prevent a perceptible jump because brightness may operate + // differently when the display is configured for dozing. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() == Display.STATE_ON) { + return; + } + + // Set screen state. + if (!setScreenState(Display.STATE_DOZE)) { + return; // screen on blocked + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_DOZE_SUSPEND) { + // Want screen dozing and suspended. + // Wait for brightness animation to complete beforehand unless already + // suspended because we may not be able to change it after suspension. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) { + return; + } + + // If not already suspending, temporarily set the state to doze until the + // screen on is unblocked, then suspend. + if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) { + if (!setScreenState(Display.STATE_DOZE)) { + return; // screen on blocked + } + setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else if (target == Display.STATE_ON_SUSPEND) { + // Want screen full-power and suspended. + // Wait for brightness animation to complete beforehand unless already + // suspended because we may not be able to change it after suspension. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) { + return; + } + + // If not already suspending, temporarily set the state to on until the + // screen on is unblocked, then suspend. + if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) { + if (!setScreenState(Display.STATE_ON)) { + return; + } + setScreenState(Display.STATE_ON_SUSPEND); + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); + } else { + // Want screen off. + mPendingScreenOff = true; + if (!mColorFadeEnabled) { + mPowerState.setColorFadeLevel(0.0f); + } + + if (mPowerState.getColorFadeLevel() == 0.0f) { + // Turn the screen off. + // A black surface is already hiding the contents of the screen. + setScreenState(Display.STATE_OFF); + mPendingScreenOff = false; + mPowerState.dismissColorFadeResources(); + } else if (performScreenOffTransition + && mPowerState.prepareColorFade(mContext, + mColorFadeFadesConfig + ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN) + && mPowerState.getScreenState() != Display.STATE_OFF) { + // Perform the screen off animation. + mColorFadeOffAnimator.start(); + } else { + // Skip the screen off animation and add a black surface to hide the + // contents of the screen. + mColorFadeOffAnimator.end(); + } + } + } + + private final Runnable mCleanListener = this::sendUpdatePowerState; + + private void setProximitySensorEnabled(boolean enable) { + if (enable) { + if (!mProximitySensorEnabled) { + // Register the listener. + // Proximity sensor state already cleared initially. + mProximitySensorEnabled = true; + mIgnoreProximityUntilChanged = false; + mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + } + } else { + if (mProximitySensorEnabled) { + // Unregister the listener. + // Clear the proximity sensor state for next time. + mProximitySensorEnabled = false; + mProximity = PROXIMITY_UNKNOWN; + mIgnoreProximityUntilChanged = false; + mPendingProximity = PROXIMITY_UNKNOWN; + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mProximitySensorListener); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } + } + } + + private void handleProximitySensorEvent(long time, boolean positive) { + if (mProximitySensorEnabled) { + if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { + return; // no change + } + if (mPendingProximity == PROXIMITY_POSITIVE && positive) { + return; // no change + } + + // Only accept a proximity sensor reading if it remains + // stable for the entire debounce delay. We hold a wake lock while + // debouncing the sensor. + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + if (positive) { + mPendingProximity = PROXIMITY_POSITIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock + } else { + mPendingProximity = PROXIMITY_NEGATIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock + } + + // Debounce the new sensor reading. + debounceProximitySensor(); + } + } + + private void debounceProximitySensor() { + if (mProximitySensorEnabled + && mPendingProximity != PROXIMITY_UNKNOWN + && mPendingProximityDebounceTime >= 0) { + final long now = mClock.uptimeMillis(); + if (mPendingProximityDebounceTime <= now) { + if (mProximity != mPendingProximity) { + // if the status of the sensor changed, stop ignoring. + mIgnoreProximityUntilChanged = false; + Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]"); + } + // Sensor reading accepted. Apply the change then release the wake lock. + mProximity = mPendingProximity; + updatePowerState(); + clearPendingProximityDebounceTime(); // release wake lock (must be last) + } else { + // Need to wait a little longer. + // Debounce again later. We continue holding a wake lock while waiting. + Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); + } + } + } + + private void clearPendingProximityDebounceTime() { + if (mPendingProximityDebounceTime >= 0) { + mPendingProximityDebounceTime = -1; + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce); + } + } + + private void setPendingProximityDebounceTime(long debounceTime) { + if (mPendingProximityDebounceTime < 0) { + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce); + } + mPendingProximityDebounceTime = debounceTime; + } + + private void sendOnStateChangedWithWakelock() { + if (!mOnStateChangedPending) { + mOnStateChangedPending = true; + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mHandler.post(mOnStateChangedRunnable); + } + } + + private void logDisplayPolicyChanged(int newPolicy) { + LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY); + log.setType(MetricsEvent.TYPE_UPDATE); + log.setSubtype(newPolicy); + MetricsLogger.action(log); + } + + private void handleSettingsChange(boolean userSwitch) { + mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); + mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + if (userSwitch) { + // Don't treat user switches as user initiated change. + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); + updateAutoBrightnessAdjustment(); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.resetShortTermModel(); + } + } + // We don't bother with a pending variable for VR screen brightness since we just + // immediately adapt to it. + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + sendUpdatePowerState(); + } + + private float getAutoBrightnessAdjustmentSetting() { + final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT); + return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj); + } + + @Override + public float getScreenBrightnessSetting() { + float brightness = mBrightnessSetting.getBrightness(); + if (Float.isNaN(brightness)) { + brightness = mScreenBrightnessDefault; + } + return clampAbsoluteBrightness(brightness); + } + + private float getScreenBrightnessForVrSetting() { + final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault, + UserHandle.USER_CURRENT); + return clampScreenBrightnessForVr(brightnessFloat); + } + + @Override + public void setBrightness(float brightnessValue) { + // Update the setting, which will eventually call back into DPC to have us actually update + // the display with the new value. + mBrightnessSetting.setBrightness(brightnessValue); + } + + private void updateScreenBrightnessSetting(float brightnessValue) { + if (!isValidBrightnessValue(brightnessValue) + || brightnessValue == mCurrentScreenBrightnessSetting) { + return; + } + setCurrentScreenBrightness(brightnessValue); + mBrightnessSetting.setBrightness(brightnessValue); + } + + private void setCurrentScreenBrightness(float brightnessValue) { + if (brightnessValue != mCurrentScreenBrightnessSetting) { + mCurrentScreenBrightnessSetting = brightnessValue; + postBrightnessChangeRunnable(); + } + } + + private void putAutoBrightnessAdjustmentSetting(float adjustment) { + if (mDisplayId == Display.DEFAULT_DISPLAY) { + mAutoBrightnessAdjustment = adjustment; + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment, + UserHandle.USER_CURRENT); + } + } + + private boolean updateAutoBrightnessAdjustment() { + if (Float.isNaN(mPendingAutoBrightnessAdjustment)) { + return false; + } + if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) { + mPendingAutoBrightnessAdjustment = Float.NaN; + return false; + } + mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment; + mPendingAutoBrightnessAdjustment = Float.NaN; + mTemporaryAutoBrightnessAdjustment = Float.NaN; + return true; + } + + // We want to return true if the user has set the screen brightness. + // RBC on, off, and intensity changes will return false. + // Slider interactions whilst in RBC will return true, just as when in non-rbc. + private boolean updateUserSetScreenBrightness() { + if ((Float.isNaN(mPendingScreenBrightnessSetting) + || mPendingScreenBrightnessSetting < 0.0f)) { + return false; + } + if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) { + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + return false; + } + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); + mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting; + mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + return true; + } + + private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, + boolean hadUserDataPoint) { + final float brightnessInNits = convertToNits(brightness); + if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f + && mAutomaticBrightnessController != null) { + // We only want to track changes on devices that can actually map the display backlight + // values into a physical brightness unit since the value provided by the API is in + // nits and not using the arbitrary backlight units. + final float powerFactor = mPowerRequest.lowPowerMode + ? mPowerRequest.screenLowPowerBrightnessFactor + : 1.0f; + mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated, + powerFactor, hadUserDataPoint, + mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId); + } + } + + private float convertToNits(float brightness) { + if (mAutomaticBrightnessController == null) { + return -1f; + } + return mAutomaticBrightnessController.convertToNits(brightness); + } + + @GuardedBy("mLock") + private void updatePendingProximityRequestsLocked() { + mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + + if (mIgnoreProximityUntilChanged) { + // Also, lets stop waiting for negative proximity if we're ignoring it. + mWaitingForNegativeProximity = false; + } + } + + private void ignoreProximitySensorUntilChangedInternal() { + if (!mIgnoreProximityUntilChanged + && mProximity == PROXIMITY_POSITIVE) { + // Only ignore if it is still reporting positive (near) + mIgnoreProximityUntilChanged = true; + Slog.i(mTag, "Ignoring proximity"); + updatePowerState(); + } + } + + private final Runnable mOnStateChangedRunnable = new Runnable() { + @Override + public void run() { + mOnStateChangedPending = false; + mCallbacks.onStateChanged(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); + } + }; + + private void sendOnProximityPositiveWithWakelock() { + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive); + mHandler.post(mOnProximityPositiveRunnable); + mOnProximityPositiveMessages++; + } + + private final Runnable mOnProximityPositiveRunnable = new Runnable() { + @Override + public void run() { + mOnProximityPositiveMessages--; + mCallbacks.onProximityPositive(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); + } + }; + + private void sendOnProximityNegativeWithWakelock() { + mOnProximityNegativeMessages++; + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative); + mHandler.post(mOnProximityNegativeRunnable); + } + + private final Runnable mOnProximityNegativeRunnable = new Runnable() { + @Override + public void run() { + mOnProximityNegativeMessages--; + mCallbacks.onProximityNegative(); + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); + } + }; + + @Override + public void dump(final PrintWriter pw) { + synchronized (mLock) { + pw.println(); + pw.println("Display Power Controller:"); + pw.println(" mDisplayId=" + mDisplayId); + pw.println(" mLightSensor=" + mLightSensor); + + pw.println(); + pw.println("Display Power Controller Locked State:"); + pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked); + pw.println(" mPendingRequestLocked=" + mPendingRequestLocked); + pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked); + pw.println(" mPendingWaitForNegativeProximityLocked=" + + mPendingWaitForNegativeProximityLocked); + pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked); + } + + pw.println(); + pw.println("Display Power Controller Configuration:"); + pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault); + pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig); + pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); + pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum); + pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum); + pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault); + pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); + pw.println(" mAllowAutoBrightnessWhileDozingConfig=" + + mAllowAutoBrightnessWhileDozingConfig); + pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp); + pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); + pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); + synchronized (mCachedBrightnessInfo) { + pw.println(" mCachedBrightnessInfo.brightness=" + + mCachedBrightnessInfo.brightness.value); + pw.println(" mCachedBrightnessInfo.adjustedBrightness=" + + mCachedBrightnessInfo.adjustedBrightness.value); + pw.println(" mCachedBrightnessInfo.brightnessMin=" + + mCachedBrightnessInfo.brightnessMin.value); + pw.println(" mCachedBrightnessInfo.brightnessMax=" + + mCachedBrightnessInfo.brightnessMax.value); + pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); + pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + + mCachedBrightnessInfo.hbmTransitionPoint.value); + pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" + + mCachedBrightnessInfo.brightnessMaxReason.value); + } + pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); + pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); + + mHandler.runWithScissors(() -> dumpLocal(pw), 1000); + } + + private void dumpLocal(PrintWriter pw) { + pw.println(); + pw.println("Display Power Controller Thread State:"); + pw.println(" mPowerRequest=" + mPowerRequest); + pw.println(" mUnfinishedBusiness=" + mUnfinishedBusiness); + pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity); + pw.println(" mProximitySensor=" + mProximitySensor); + pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); + pw.println(" mProximityThreshold=" + mProximityThreshold); + pw.println(" mProximity=" + proximityToString(mProximity)); + pw.println(" mPendingProximity=" + proximityToString(mPendingProximity)); + pw.println(" mPendingProximityDebounceTime=" + + TimeUtils.formatUptime(mPendingProximityDebounceTime)); + pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); + pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness); + pw.println(" mPendingScreenBrightnessSetting=" + + mPendingScreenBrightnessSetting); + pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness); + pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); + pw.println(" mBrightnessReason=" + mBrightnessReason); + pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment); + pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment); + pw.println(" mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr); + pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness); + pw.println(" mAppliedDimming=" + mAppliedDimming); + pw.println(" mAppliedLowPower=" + mAppliedLowPower); + pw.println(" mAppliedThrottling=" + mAppliedThrottling); + pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride); + pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness); + pw.println(" mAppliedTemporaryAutoBrightnessAdjustment=" + + mAppliedTemporaryAutoBrightnessAdjustment); + pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost); + pw.println(" mDozing=" + mDozing); + pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState)); + pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime); + pw.println(" mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime); + pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker); + pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker); + pw.println(" mPendingScreenOff=" + mPendingScreenOff); + pw.println(" mReportedToPolicy=" + + reportedToPolicyToString(mReportedScreenStateToPolicy)); + pw.println(" mIsRbcActive=" + mIsRbcActive); + pw.println(" mOnStateChangePending=" + mOnStateChangedPending); + pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages); + pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages); + + if (mScreenBrightnessRampAnimator != null) { + pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + + mScreenBrightnessRampAnimator.isAnimating()); + } + + if (mColorFadeOnAnimator != null) { + pw.println(" mColorFadeOnAnimator.isStarted()=" + + mColorFadeOnAnimator.isStarted()); + } + if (mColorFadeOffAnimator != null) { + pw.println(" mColorFadeOffAnimator.isStarted()=" + + mColorFadeOffAnimator.isStarted()); + } + + if (mPowerState != null) { + mPowerState.dump(pw); + } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.dump(pw); + dumpBrightnessEvents(pw); + } + + if (mHbmController != null) { + mHbmController.dump(pw); + } + + if (mBrightnessThrottler != null) { + mBrightnessThrottler.dump(pw); + } + + pw.println(); + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.dump(pw); + mDisplayWhiteBalanceSettings.dump(pw); + } + } + + private static String proximityToString(int state) { + switch (state) { + case PROXIMITY_UNKNOWN: + return "Unknown"; + case PROXIMITY_NEGATIVE: + return "Negative"; + case PROXIMITY_POSITIVE: + return "Positive"; + default: + return Integer.toString(state); + } + } + + private static String reportedToPolicyToString(int state) { + switch (state) { + case REPORTED_TO_POLICY_SCREEN_OFF: + return "REPORTED_TO_POLICY_SCREEN_OFF"; + case REPORTED_TO_POLICY_SCREEN_TURNING_ON: + return "REPORTED_TO_POLICY_SCREEN_TURNING_ON"; + case REPORTED_TO_POLICY_SCREEN_ON: + return "REPORTED_TO_POLICY_SCREEN_ON"; + default: + return Integer.toString(state); + } + } + + private static String skipRampStateToString(int state) { + switch (state) { + case RAMP_STATE_SKIP_NONE: + return "RAMP_STATE_SKIP_NONE"; + case RAMP_STATE_SKIP_INITIAL: + return "RAMP_STATE_SKIP_INITIAL"; + case RAMP_STATE_SKIP_AUTOBRIGHT: + return "RAMP_STATE_SKIP_AUTOBRIGHT"; + default: + return Integer.toString(state); + } + } + + private void dumpBrightnessEvents(PrintWriter pw) { + int size = mBrightnessEventRingBuffer.size(); + if (size < 1) { + pw.println("No Automatic Brightness Adjustments"); + return; + } + + pw.println("Automatic Brightness Adjustments Last " + size + " Events: "); + BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray(); + for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) { + pw.println(" " + eventArray[i].toString()); + } + } + + private static float clampAbsoluteBrightness(float value) { + return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX); + } + + private static float clampAutoBrightnessAdjustment(float value) { + return MathUtils.constrain(value, -1.0f, 1.0f); + } + + private void noteScreenState(int screenState) { + if (mBatteryStats != null) { + try { + // TODO(multi-display): make this multi-display + mBatteryStats.noteScreenState(screenState); + } catch (RemoteException e) { + // same process + } + } + } + + private void noteScreenBrightness(float brightness) { + if (mBatteryStats != null) { + try { + // TODO(brightnessfloat): change BatteryStats to use float + mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt( + brightness)); + } catch (RemoteException e) { + // same process + } + } + } + + private void reportStats(float brightness) { + if (mLastStatsBrightness == brightness) { + return; + } + + float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; + synchronized (mCachedBrightnessInfo) { + if (mCachedBrightnessInfo.hbmTransitionPoint == null) { + return; + } + hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value; + } + + final boolean aboveTransition = brightness > hbmTransitionPoint; + final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint; + + if (aboveTransition || oldAboveTransition) { + mLastStatsBrightness = brightness; + mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS); + if (aboveTransition != oldAboveTransition) { + // report immediately + logHbmBrightnessStats(brightness, mDisplayStatsId); + } else { + // delay for rate limiting + Message msg = mHandler.obtainMessage(); + msg.what = MSG_STATSD_HBM_BRIGHTNESS; + msg.arg1 = Float.floatToIntBits(brightness); + msg.arg2 = mDisplayStatsId; + mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS); + } + } + } + + private void logHbmBrightnessStats(float brightness, int displayStatsId) { + synchronized (mHandler) { + FrameworkStatsLog.write( + FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness); + } + } + + private void logManualBrightnessEvent(BrightnessEvent event) { + float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f; + int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1; + float appliedHbmMaxNits = + event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF + ? -1f : convertToNits(event.getHbmMax()); + // thermalCapNits set to -1 if not currently capping max brightness + float appliedThermalCapNits = + event.getThermalMax() == PowerManager.BRIGHTNESS_MAX + ? -1f : convertToNits(event.getThermalMax()); + + FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, + convertToNits(event.getInitialBrightness()), + convertToNits(event.getBrightness()), + event.getSlowAmbientLux(), + event.getPhysicalDisplayId(), + event.isShortTermModelActive(), + appliedLowPowerMode, + appliedRbcStrength, + appliedHbmMaxNits, + appliedThermalCapNits, + event.isAutomaticBrightnessEnabled(), + FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL); + } + + private final class DisplayControllerHandler extends Handler { + DisplayControllerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_POWER_STATE: + updatePowerState(); + break; + + case MSG_PROXIMITY_SENSOR_DEBOUNCED: + debounceProximitySensor(); + break; + + case MSG_SCREEN_ON_UNBLOCKED: + if (mPendingScreenOnUnblocker == msg.obj) { + unblockScreenOn(); + updatePowerState(); + } + break; + case MSG_SCREEN_OFF_UNBLOCKED: + if (mPendingScreenOffUnblocker == msg.obj) { + unblockScreenOff(); + updatePowerState(); + } + break; + case MSG_CONFIGURE_BRIGHTNESS: + mBrightnessConfiguration = (BrightnessConfiguration) msg.obj; + updatePowerState(); + break; + + case MSG_SET_TEMPORARY_BRIGHTNESS: + // TODO: Should we have a a timeout for the temporary brightness? + mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1); + updatePowerState(); + break; + + case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT: + mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1); + updatePowerState(); + break; + + case MSG_IGNORE_PROXIMITY: + ignoreProximitySensorUntilChangedInternal(); + break; + + case MSG_STOP: + cleanupHandlerThreadAfterStop(); + break; + + case MSG_UPDATE_BRIGHTNESS: + if (mStopped) { + return; + } + handleSettingsChange(false /*userSwitch*/); + break; + + case MSG_UPDATE_RBC: + handleRbcChanged(); + break; + + case MSG_BRIGHTNESS_RAMP_DONE: + if (mPowerState != null) { + final float brightness = mPowerState.getScreenBrightness(); + reportStats(brightness); + } + break; + + case MSG_STATSD_HBM_BRIGHTNESS: + logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2); + break; + } + } + } + + private final SensorEventListener mProximitySensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mProximitySensorEnabled) { + final long time = mClock.uptimeMillis(); + final float distance = event.values[0]; + boolean positive = distance >= 0.0f && distance < mProximityThreshold; + handleProximitySensorEvent(time, positive); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + handleSettingsChange(false /* userSwitch */); + } + } + + private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener { + @Override + public void onScreenOn() { + Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this); + mHandler.sendMessage(msg); + } + } + + private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener { + @Override + public void onScreenOff() { + Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this); + mHandler.sendMessage(msg); + } + } + + @Override + public void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.setLoggingEnabled(enabled); + } + } + + @Override // DisplayWhiteBalanceController.Callbacks + public void updateWhiteBalance() { + sendUpdatePowerState(); + } + + @Override + public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setLoggingEnabled(enabled); + mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled); + } + } + + @Override + public void setAmbientColorTemperatureOverride(float cct) { + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct); + // The ambient color temperature override is only applied when the ambient color + // temperature changes or is updated, so it doesn't necessarily change the screen color + // temperature immediately. So, let's make it! + sendUpdatePowerState(); + } + } + + @VisibleForTesting + String getSuspendBlockerUnfinishedBusinessId(int displayId) { + return "[" + displayId + "]unfinished business"; + } + + String getSuspendBlockerOnStateChangedId(int displayId) { + return "[" + displayId + "]on state changed"; + } + + String getSuspendBlockerProxPositiveId(int displayId) { + return "[" + displayId + "]prox positive"; + } + + String getSuspendBlockerProxNegativeId(int displayId) { + return "[" + displayId + "]prox negative"; + } + + @VisibleForTesting + String getSuspendBlockerProxDebounceId(int displayId) { + return "[" + displayId + "]prox debounce"; + } + + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + + @VisibleForTesting + static class Injector { + Clock getClock() { + return SystemClock::uptimeMillis; + } + + DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, + int displayId, int displayState) { + return new DisplayPowerState(blanker, colorFade, displayId, displayState); + } + + DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps, + FloatProperty<DisplayPowerState> firstProperty, + FloatProperty<DisplayPowerState> secondProperty) { + return new DualRampAnimator(dps, firstProperty, secondProperty); + } + } + + static class CachedBrightnessInfo { + public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat adjustedBrightness = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMin = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMax = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); + public MutableFloat hbmTransitionPoint = + new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); + public MutableInt brightnessMaxReason = + new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); + + public boolean checkAndSetFloat(MutableFloat mf, float f) { + if (mf.value != f) { + mf.value = f; + return true; + } + return false; + } + + public boolean checkAndSetInt(MutableInt mi, int i) { + if (mi.value != i) { + mi.value = i; + return true; + } + return false; + } + } +} diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index 34134892552f..abf8fe3d3f17 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -36,8 +36,7 @@ public class HysteresisLevels { private final float mMinBrightening; /** - * Creates a {@code HysteresisLevels} object with the given equal-length - * integer arrays. + * Creates a {@code HysteresisLevels} object for ambient brightness. * @param brighteningThresholds an array of brightening hysteresis constraint constants. * @param darkeningThresholds an array of darkening hysteresis constraint constants. * @param thresholdLevels a monotonically increasing array of threshold levels. @@ -59,6 +58,28 @@ public class HysteresisLevels { } /** + * Creates a {@code HysteresisLevels} object for screen brightness. + * @param brighteningThresholds an array of brightening hysteresis constraint constants. + * @param darkeningThresholds an array of darkening hysteresis constraint constants. + * @param thresholdLevels a monotonically increasing array of threshold levels. + * @param minBrighteningThreshold the minimum value for which the brightening value needs to + * return. + * @param minDarkeningThreshold the minimum value for which the darkening value needs to return. + */ + HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds, + float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) { + if (brighteningThresholds.length != darkeningThresholds.length + || darkeningThresholds.length != thresholdLevels.length + 1) { + throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); + } + mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f); + mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f); + mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels); + mMinDarkening = minDarkeningThreshold; + mMinBrightening = minBrighteningThreshold; + } + + /** * Return the brightening hysteresis threshold for the given value level. */ public float getBrighteningThreshold(float value) { @@ -104,11 +125,42 @@ public class HysteresisLevels { private float[] setArrayFormat(int[] configArray, float divideFactor) { float[] levelArray = new float[configArray.length]; for (int index = 0; levelArray.length > index; ++index) { - levelArray[index] = (float)configArray[index] / divideFactor; + levelArray[index] = (float) configArray[index] / divideFactor; } return levelArray; } + /** + * This check is due to historical reasons, where screen thresholdLevels used to be + * integer values in the range of [0-255], but then was changed to be float values from [0,1]. + * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0, + * 1], and if not, we divide all the levels with 255 to bring them down to the same scale. + */ + private float[] constraintInRangeIfNeeded(float[] thresholdLevels) { + if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */ + 1.0f)) { + return thresholdLevels; + } + + Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale"); + float[] thresholdLevelsScaled = new float[thresholdLevels.length]; + for (int index = 0; thresholdLevels.length > index; ++index) { + thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f; + } + return thresholdLevelsScaled; + } + + private boolean isAllInRange(float[] configArray, float minValueInclusive, + float maxValueInclusive) { + int configArraySize = configArray.length; + for (int index = 0; configArraySize > index; ++index) { + if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) { + return false; + } + } + return true; + } + void dump(PrintWriter pw) { pw.println("HysteresisLevels"); pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds)); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3ee35036e9fd..e03a46c7850e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -266,6 +266,10 @@ public class HdmiControlService extends SystemService { // Make sure HdmiCecConfig is instantiated and the XMLs are read. private HdmiCecConfig mHdmiCecConfig; + // Last return value of getPhysicalAddress(). Only updated on calls of getPhysicalAddress(). + // Does not represent the current physical address at all times. Not to be used as a cache. + private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; + /** * Interface to report send result. */ @@ -2078,9 +2082,15 @@ public class HdmiControlService extends SystemService { @Override public int getPhysicalAddress() { initBinderCall(); - synchronized (mLock) { - return mHdmiCecNetwork.getPhysicalAddress(); - } + runOnServiceThread(new Runnable() { + @Override + public void run() { + synchronized (mLock) { + mPhysicalAddress = mHdmiCecNetwork.getPhysicalAddress(); + } + } + }); + return mPhysicalAddress; } @Override diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 86732a12ec2c..32f17dc18033 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -55,6 +55,9 @@ final class BatteryController implements InputManager.InputDeviceListener { // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart) private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + @VisibleForTesting + static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds + private final Object mLock = new Object(); private final Context mContext; private final NativeInputManagerService mNative; @@ -71,6 +74,11 @@ final class BatteryController implements InputManager.InputDeviceListener { @GuardedBy("mLock") private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>(); + @GuardedBy("mLock") + private boolean mIsPolling = false; + @GuardedBy("mLock") + private boolean mIsInteractive = true; + BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) { this(context, nativeService, looper, new UEventManager() {}); } @@ -135,6 +143,7 @@ final class BatteryController implements InputManager.InputDeviceListener { + " is monitoring deviceId " + deviceId); } + updatePollingLocked(true /*delayStart*/); notifyBatteryListener(listenerRecord, deviceState); } } @@ -162,6 +171,23 @@ final class BatteryController implements InputManager.InputDeviceListener { }); } + @GuardedBy("mLock") + private void updatePollingLocked(boolean delayStart) { + if (mMonitoredDeviceStates.isEmpty() || !mIsInteractive) { + // Stop polling. + mIsPolling = false; + mHandler.removeCallbacks(this::handlePollEvent); + return; + } + + if (mIsPolling) { + return; + } + // Start polling. + mIsPolling = true; + mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0); + } + private boolean hasBattery(int deviceId) { final InputDevice device = Objects.requireNonNull(mContext.getSystemService(InputManager.class)) @@ -232,6 +258,8 @@ final class BatteryController implements InputManager.InputDeviceListener { mListenerRecords.remove(pid); if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid); } + + updatePollingLocked(false /*delayStart*/); } @GuardedBy("mLock") @@ -273,10 +301,37 @@ final class BatteryController implements InputManager.InputDeviceListener { } } + private void handlePollEvent() { + synchronized (mLock) { + if (!mIsPolling) { + return; + } + final long eventTime = SystemClock.uptimeMillis(); + mMonitoredDeviceStates.forEach((deviceId, deviceState) -> { + // Re-acquire lock in the lambda to silence error-prone build warnings. + synchronized (mLock) { + if (deviceState.updateBatteryState(eventTime)) { + notifyAllListenersForDeviceLocked(deviceState); + } + } + }); + mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS); + } + } + + void onInteractiveChanged(boolean interactive) { + synchronized (mLock) { + mIsInteractive = interactive; + updatePollingLocked(false /*delayStart*/); + } + } + void dump(PrintWriter pw, String prefix) { synchronized (mLock) { - pw.println(prefix + TAG + ": " + mListenerRecords.size() - + " battery listeners"); + pw.println(prefix + TAG + ": " + + mListenerRecords.size() + " battery listeners" + + ", Polling = " + mIsPolling + + ", Interactive = " + mIsInteractive); for (int i = 0; i < mListenerRecords.size(); i++) { pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i)); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 4da0c0df390c..05c4f77d5c81 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3684,6 +3684,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void setInteractive(boolean interactive) { mNative.setInteractive(interactive); + mBatteryController.onInteractiveChanged(interactive); } @Override diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 1d1057f8ae9f..9bd48f2e0a58 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -115,6 +115,7 @@ import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationPermissionsHelper; import com.android.server.location.injector.LocationPowerSaveModeHelper; import com.android.server.location.injector.LocationUsageLogger; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.ScreenInteractiveHelper; import com.android.server.location.injector.SettingsHelper; import com.android.server.location.injector.SystemAlarmHelper; @@ -125,6 +126,7 @@ import com.android.server.location.injector.SystemDeviceStationaryHelper; import com.android.server.location.injector.SystemEmergencyHelper; import com.android.server.location.injector.SystemLocationPermissionsHelper; import com.android.server.location.injector.SystemLocationPowerSaveModeHelper; +import com.android.server.location.injector.SystemPackageResetHelper; import com.android.server.location.injector.SystemScreenInteractiveHelper; import com.android.server.location.injector.SystemSettingsHelper; import com.android.server.location.injector.SystemUserInfoHelper; @@ -1696,11 +1698,13 @@ public class LocationManagerService extends ILocationManager.Stub implements private final SystemDeviceStationaryHelper mDeviceStationaryHelper; private final SystemDeviceIdleHelper mDeviceIdleHelper; private final LocationUsageLogger mLocationUsageLogger; + private final PackageResetHelper mPackageResetHelper; // lazily instantiated since they may not always be used @GuardedBy("this") - private @Nullable SystemEmergencyHelper mEmergencyCallHelper; + @Nullable + private SystemEmergencyHelper mEmergencyCallHelper; @GuardedBy("this") private boolean mSystemReady; @@ -1721,6 +1725,7 @@ public class LocationManagerService extends ILocationManager.Stub implements mDeviceStationaryHelper = new SystemDeviceStationaryHelper(); mDeviceIdleHelper = new SystemDeviceIdleHelper(context); mLocationUsageLogger = new LocationUsageLogger(); + mPackageResetHelper = new SystemPackageResetHelper(context); } synchronized void onSystemReady() { @@ -1811,5 +1816,10 @@ public class LocationManagerService extends ILocationManager.Stub implements public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } + + @Override + public PackageResetHelper getPackageResetHelper() { + return mPackageResetHelper; + } } } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 82bcca2b8470..e7f6e67f13ad 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -39,6 +39,7 @@ import com.android.server.LocalServices; import com.android.server.location.injector.AppForegroundHelper; import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationPermissionsHelper; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.SettingsHelper; import com.android.server.location.injector.UserInfoHelper; import com.android.server.location.injector.UserInfoHelper.UserListener; @@ -193,6 +194,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final AppForegroundHelper mAppForegroundHelper; protected final LocationManagerInternal mLocationManagerInternal; + private final PackageResetHelper mPackageResetHelper; private final UserListener mUserChangedListener = this::onUserChanged; private final ProviderEnabledListener mProviderEnabledChangedListener = @@ -218,12 +220,25 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter }; private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener = this::onAppForegroundChanged; + private final PackageResetHelper.Responder mPackageResetResponder = + new PackageResetHelper.Responder() { + @Override + public void onPackageReset(String packageName) { + GnssListenerMultiplexer.this.onPackageReset(packageName); + } + + @Override + public boolean isResetableForPackage(String packageName) { + return GnssListenerMultiplexer.this.isResetableForPackage(packageName); + } + }; protected GnssListenerMultiplexer(Injector injector) { mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mAppForegroundHelper = injector.getAppForegroundHelper(); + mPackageResetHelper = injector.getPackageResetHelper(); mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); } @@ -357,6 +372,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPackageBlacklistChangedListener); mLocationPermissionsHelper.addListener(mLocationPermissionsListener); mAppForegroundHelper.addListener(mAppForegroundChangedListener); + mPackageResetHelper.register(mPackageResetResponder); } @Override @@ -374,6 +390,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPackageBlacklistChangedListener); mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); mAppForegroundHelper.removeListener(mAppForegroundChangedListener); + mPackageResetHelper.unregister(mPackageResetResponder); } private void onUserChanged(int userId, int change) { @@ -407,6 +424,27 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground)); } + private void onPackageReset(String packageName) { + // invoked when a package is "force quit" - move off the main thread + FgThread.getExecutor().execute( + () -> + updateRegistrations( + registration -> { + if (registration.getIdentity().getPackageName().equals( + packageName)) { + registration.remove(); + } + + return false; + })); + } + + private boolean isResetableForPackage(String packageName) { + // invoked to find out if the given package has any state that can be "force quit" + return findRegistration( + registration -> registration.getIdentity().getPackageName().equals(packageName)); + } + @Override protected String getServiceState() { if (!isSupported()) { diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java index c0ce3a6853cf..b2c86721fdcc 100644 --- a/services/core/java/com/android/server/location/injector/Injector.java +++ b/services/core/java/com/android/server/location/injector/Injector.java @@ -63,4 +63,7 @@ public interface Injector { /** Returns a LocationUsageLogger. */ LocationUsageLogger getLocationUsageLogger(); + + /** Returns a PackageResetHelper. */ + PackageResetHelper getPackageResetHelper(); } diff --git a/services/core/java/com/android/server/location/injector/PackageResetHelper.java b/services/core/java/com/android/server/location/injector/PackageResetHelper.java new file mode 100644 index 000000000000..721c576b1b10 --- /dev/null +++ b/services/core/java/com/android/server/location/injector/PackageResetHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.injector; + +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; + +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** Helpers for tracking queries and resets of package state. */ +public abstract class PackageResetHelper { + + /** Interface for responding to reset events. */ + public interface Responder { + + /** + * Called when a package's runtime state is being reset for whatever reason and any + * components carrying runtime state on behalf of the package should clear that state. + * + * @param packageName The name of the package. + */ + void onPackageReset(String packageName); + + /** + * Called when the system queries whether this package has any active state for the given + * package. Should return true if the component has some runtime state that is resetable of + * behalf of the given package, and false otherwise. + * + * @param packageName The name of the package. + * @return True if this component has resetable state for the given package. + */ + boolean isResetableForPackage(String packageName); + } + + private final CopyOnWriteArrayList<Responder> mResponders; + + public PackageResetHelper() { + mResponders = new CopyOnWriteArrayList<>(); + } + + /** Begin listening for package reset events. */ + public synchronized void register(Responder responder) { + boolean empty = mResponders.isEmpty(); + mResponders.add(responder); + if (empty) { + onRegister(); + } + } + + /** Stop listening for package reset events. */ + public synchronized void unregister(Responder responder) { + mResponders.remove(responder); + if (mResponders.isEmpty()) { + onUnregister(); + } + } + + @GuardedBy("this") + protected abstract void onRegister(); + + @GuardedBy("this") + protected abstract void onUnregister(); + + protected final void notifyPackageReset(String packageName) { + if (D) { + Log.d(TAG, "package " + packageName + " reset"); + } + + for (Responder responder : mResponders) { + responder.onPackageReset(packageName); + } + } + + protected final boolean queryResetableForPackage(String packageName) { + for (Responder responder : mResponders) { + if (responder.isResetableForPackage(packageName)) { + return true; + } + } + + return false; + } +} diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java new file mode 100644 index 000000000000..91b0212df734 --- /dev/null +++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.injector; + +import android.annotation.Nullable; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; + +import com.android.internal.util.Preconditions; + +/** Listens to appropriate broadcasts for queries and resets. */ +public class SystemPackageResetHelper extends PackageResetHelper { + + private final Context mContext; + + @Nullable + private BroadcastReceiver mReceiver; + + public SystemPackageResetHelper(Context context) { + mContext = context; + } + + @Override + protected void onRegister() { + Preconditions.checkState(mReceiver == null); + mReceiver = new Receiver(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + filter.addDataScheme("package"); + + // We don't filter for Intent.ACTION_PACKAGE_DATA_CLEARED as 1) it refers to persistent + // data, and 2) it should always be preceded by Intent.ACTION_PACKAGE_RESTARTED, which + // refers to runtime data. in this way we also avoid redundant callbacks. + + mContext.registerReceiver(mReceiver, filter); + } + + @Override + protected void onUnregister() { + Preconditions.checkState(mReceiver != null); + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + + private class Receiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + Uri data = intent.getData(); + if (data == null) { + return; + } + + String packageName = data.getSchemeSpecificPart(); + if (packageName == null) { + return; + } + + switch (action) { + case Intent.ACTION_QUERY_PACKAGE_RESTART: + String[] packages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + if (packages != null) { + // it would be more efficient to pass through the whole array, but at the + // moment the array is always size 1, and this makes for a nicer callback. + for (String pkg : packages) { + if (queryResetableForPackage(pkg)) { + setResultCode(Activity.RESULT_OK); + break; + } + } + } + break; + case Intent.ACTION_PACKAGE_CHANGED: + // make sure this is an enabled/disabled change to the package as a whole, not + // just some of its components. This avoids unnecessary work in the callback. + boolean isPackageChange = false; + String[] components = intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (components != null) { + for (String component : components) { + if (packageName.equals(component)) { + isPackageChange = true; + break; + } + } + } + + if (isPackageChange) { + try { + ApplicationInfo appInfo = + context.getPackageManager().getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of(0)); + if (!appInfo.enabled) { + notifyPackageReset(packageName); + } + } catch (PackageManager.NameNotFoundException e) { + return; + } + } + break; + case Intent.ACTION_PACKAGE_REMOVED: + // fall through + case Intent.ACTION_PACKAGE_RESTARTED: + notifyPackageReset(packageName); + break; + default: + break; + } + } + } +} diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index a69a079b679d..bd7525133624 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -106,6 +106,7 @@ import com.android.server.location.injector.LocationPermissionsHelper.LocationPe import com.android.server.location.injector.LocationPowerSaveModeHelper; import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener; import com.android.server.location.injector.LocationUsageLogger; +import com.android.server.location.injector.PackageResetHelper; import com.android.server.location.injector.ScreenInteractiveHelper; import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener; import com.android.server.location.injector.SettingsHelper; @@ -1373,6 +1374,7 @@ public class LocationProviderManager extends protected final ScreenInteractiveHelper mScreenInteractiveHelper; protected final LocationUsageLogger mLocationUsageLogger; protected final LocationFudger mLocationFudger; + private final PackageResetHelper mPackageResetHelper; private final UserListener mUserChangedListener = this::onUserChanged; private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener = @@ -1407,6 +1409,18 @@ public class LocationProviderManager extends this::onLocationPowerSaveModeChanged; private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener = this::onScreenInteractiveChanged; + private final PackageResetHelper.Responder mPackageResetResponder = + new PackageResetHelper.Responder() { + @Override + public void onPackageReset(String packageName) { + LocationProviderManager.this.onPackageReset(packageName); + } + + @Override + public boolean isResetableForPackage(String packageName) { + return LocationProviderManager.this.isResetableForPackage(packageName); + } + }; // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary protected final MockableLocationProvider mProvider; @@ -1442,6 +1456,7 @@ public class LocationProviderManager extends mScreenInteractiveHelper = injector.getScreenInteractiveHelper(); mLocationUsageLogger = injector.getLocationUsageLogger(); mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM()); + mPackageResetHelper = injector.getPackageResetHelper(); mProvider = new MockableLocationProvider(mMultiplexerLock); @@ -1970,6 +1985,7 @@ public class LocationProviderManager extends mAppForegroundHelper.addListener(mAppForegroundChangedListener); mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener); mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener); + mPackageResetHelper.register(mPackageResetResponder); } @GuardedBy("mMultiplexerLock") @@ -1988,6 +2004,7 @@ public class LocationProviderManager extends mAppForegroundHelper.removeListener(mAppForegroundChangedListener); mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener); mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener); + mPackageResetHelper.unregister(mPackageResetResponder); } @GuardedBy("mMultiplexerLock") @@ -2391,6 +2408,27 @@ public class LocationProviderManager extends updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } + private void onPackageReset(String packageName) { + // invoked when a package is "force quit" - move off the main thread + FgThread.getExecutor().execute( + () -> + updateRegistrations( + registration -> { + if (registration.getIdentity().getPackageName().equals( + packageName)) { + registration.remove(); + } + + return false; + })); + } + + private boolean isResetableForPackage(String packageName) { + // invoked to find out if the given package has any state that can be "force quit" + return findRegistration( + registration -> registration.getIdentity().getPackageName().equals(packageName)); + } + @GuardedBy("mMultiplexerLock") @Override public void onStateChanged( diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index cbbb3362a387..fdc5bab7c6eb 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -40,6 +40,8 @@ import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ILogAccessDialogCallback; +import com.android.internal.app.LogAccessDialogActivity; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -101,7 +103,7 @@ public final class LogcatManagerService extends SystemService { private final Injector mInjector; private final Supplier<Long> mClock; private final BinderService mBinderService; - private final LogcatManagerServiceInternal mLocalService; + private final LogAccessDialogCallback mDialogCallback; private final Handler mHandler; private ActivityManagerInternal mActivityManagerInternal; private ILogd mLogdService; @@ -206,7 +208,8 @@ public final class LogcatManagerService extends SystemService { } } - final class LogcatManagerServiceInternal { + final class LogAccessDialogCallback extends ILogAccessDialogCallback.Stub { + @Override public void approveAccessForClient(int uid, @NonNull String packageName) { final LogAccessClient client = new LogAccessClient(uid, packageName); if (DEBUG) { @@ -216,6 +219,7 @@ public final class LogcatManagerService extends SystemService { mHandler.sendMessageAtTime(msg, mClock.get()); } + @Override public void declineAccessForClient(int uid, @NonNull String packageName) { final LogAccessClient client = new LogAccessClient(uid, packageName); if (DEBUG) { @@ -302,7 +306,7 @@ public final class LogcatManagerService extends SystemService { mInjector = injector; mClock = injector.createClock(); mBinderService = new BinderService(); - mLocalService = new LogcatManagerServiceInternal(); + mDialogCallback = new LogAccessDialogCallback(); mHandler = new LogAccessRequestHandler(injector.getLooper(), this); } @@ -311,15 +315,14 @@ public final class LogcatManagerService extends SystemService { try { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); publishBinderService("logcat", mBinderService); - publishLocalService(LogcatManagerServiceInternal.class, mLocalService); } catch (Throwable t) { Slog.e(TAG, "Could not start the LogcatManagerService.", t); } } @VisibleForTesting - LogcatManagerServiceInternal getLocalService() { - return mLocalService; + LogAccessDialogCallback getDialogCallback() { + return mDialogCallback; } @VisibleForTesting @@ -438,6 +441,7 @@ public final class LogcatManagerService extends SystemService { mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client), mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS); final Intent mIntent = createIntent(client); + mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM); } @@ -538,6 +542,7 @@ public final class LogcatManagerService extends SystemService { intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName); intent.putExtra(Intent.EXTRA_UID, client.mUid); + intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder()); return intent; } diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 9a190316f4eb..b1e8b407d6e8 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -246,10 +246,13 @@ final class MediaButtonReceiverHolder { @Override public String toString() { - if (mPendingIntent != null) { - return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}"; - } - return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}"; + StringBuilder sb = new StringBuilder(); + sb.append("MBR {pi=").append(mPendingIntent); + sb.append(", componentName=").append(mComponentName); + sb.append(", type=").append(mComponentType); + sb.append(", pkg=").append(mPackageName); + sb.append("}"); + return sb.toString(); } /** diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index b688e0922d49..c8697b48b515 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -41,6 +41,8 @@ import java.util.Collections; final class MediaRoute2ProviderWatcher { private static final String TAG = "MR2ProviderWatcher"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE = + PackageManager.ResolveInfoFlags.of(0); private final Context mContext; private final Callback mCallback; @@ -110,8 +112,9 @@ final class MediaRoute2ProviderWatcher { // Reorder the list so that providers left at the end will be the ones to remove. int targetIndex = 0; Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE); - for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser( - intent, 0, mUserId)) { + for (ResolveInfo resolveInfo : + mPackageManager.queryIntentServicesAsUser( + intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) { ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo != null) { int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index bfa8af957208..82b5c11cfb8c 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -601,6 +601,26 @@ class MediaRouter2ServiceImpl { } } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "MediaRouter2ServiceImpl"); + + String indent = prefix + " "; + + synchronized (mLock) { + pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get()); + pw.println(indent + "mCurrentUserId=" + mCurrentUserId); + + pw.println(indent + "UserRecords:"); + if (mUserRecords.size() > 0) { + for (int i = 0; i < mUserRecords.size(); i++) { + mUserRecords.valueAt(i).dump(pw, indent + " "); + } + } else { + pw.println(indent + "<no user records>"); + } + } + } + //TODO(b/136703681): Review this is handling multi-user properly. void switchUser() { synchronized (mLock) { @@ -1197,6 +1217,36 @@ class MediaRouter2ServiceImpl { } return null; } + + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "UserRecord"); + + String indent = prefix + " "; + + pw.println(indent + "mUserId=" + mUserId); + + pw.println(indent + "Router Records:"); + if (!mRouterRecords.isEmpty()) { + for (RouterRecord routerRecord : mRouterRecords) { + routerRecord.dump(pw, indent + " "); + } + } else { + pw.println(indent + "<no router records>"); + } + + pw.println(indent + "Manager Records:"); + if (!mManagerRecords.isEmpty()) { + for (ManagerRecord managerRecord : mManagerRecords) { + managerRecord.dump(pw, indent + " "); + } + } else { + pw.println(indent + "<no manager records>"); + } + + if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) { + pw.println(indent + "<could not dump handler state>"); + } + } } final class RouterRecord implements IBinder.DeathRecipient { @@ -1236,6 +1286,22 @@ class MediaRouter2ServiceImpl { public void binderDied() { routerDied(this); } + + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "RouterRecord"); + + String indent = prefix + " "; + + pw.println(indent + "mPackageName=" + mPackageName); + pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers); + pw.println(indent + "mUid=" + mUid); + pw.println(indent + "mPid=" + mPid); + pw.println(indent + "mHasConfigureWifiDisplayPermission=" + + mHasConfigureWifiDisplayPermission); + pw.println(indent + "mHasModifyAudioRoutingPermission=" + + mHasModifyAudioRoutingPermission); + pw.println(indent + "mRouterId=" + mRouterId); + } } final class ManagerRecord implements IBinder.DeathRecipient { @@ -1267,8 +1333,20 @@ class MediaRouter2ServiceImpl { managerDied(this); } - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "ManagerRecord"); + + String indent = prefix + " "; + + pw.println(indent + "mPackageName=" + mPackageName); + pw.println(indent + "mManagerId=" + mManagerId); + pw.println(indent + "mUid=" + mUid); + pw.println(indent + "mPid=" + mPid); + pw.println(indent + "mIsScanning=" + mIsScanning); + + if (mLastSessionCreationRequest != null) { + mLastSessionCreationRequest.dump(pw, indent); + } } public void startScan() { @@ -1455,6 +1533,15 @@ class MediaRouter2ServiceImpl { } } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "UserHandler"); + + String indent = prefix + " "; + pw.println(indent + "mRunning=" + mRunning); + + mWatcher.dump(pw, prefix); + } + private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) { MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo(); @@ -2340,5 +2427,14 @@ class MediaRouter2ServiceImpl { mOldSession = oldSession; mRoute = route; } + + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(prefix + "SessionCreationRequest"); + + String indent = prefix + " "; + + pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId); + pw.println(indent + "mManagerRequestId=" + mManagerRequestId); + } } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 4806b522eca6..4f0da7952867 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -387,6 +387,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub userRecord.dump(pw, ""); } } + + pw.println(); + mService2.dump(pw, ""); } // Binder call diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index 8e944b7a965d..bea616869014 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -267,8 +267,8 @@ class IdmapDaemon { try { SystemService.start(IDMAP_DAEMON); } catch (RuntimeException e) { + Slog.wtf(TAG, "Failed to enable idmap2 daemon", e); if (e.getMessage().contains("failed to set system property")) { - Slog.w(TAG, "Failed to enable idmap2 daemon", e); return null; } } diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java index 4dd9c49321b2..672ae2eb0fb2 100644 --- a/services/core/java/com/android/server/pm/ApexPackageInfo.java +++ b/services/core/java/com/android/server/pm/ApexPackageInfo.java @@ -329,17 +329,15 @@ class ApexPackageInfo { ApexInfo ai = parsingApexInfo.get(parseResult.scanFile); if (throwable == null) { - // Calling hideAsFinal to assign derived fields for the app info flags. - parseResult.parsedPackage.hideAsFinal(); - // TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire // calling path code ScanPackageUtils.applyPolicy(parseResult.parsedPackage, PackageManagerService.SCAN_AS_SYSTEM, mPackageManager == null ? null : mPackageManager.getPlatformPackage(), false); - results.add(new ApexManager.ScanResult( - ai, parseResult.parsedPackage, parseResult.parsedPackage.getPackageName())); + // Calling hideAsFinal to assign derived fields for the app info flags. + AndroidPackage finalPkg = parseResult.parsedPackage.hideAsFinal(); + results.add(new ApexManager.ScanResult(ai, finalPkg, finalPkg.getPackageName())); } else if (throwable instanceof PackageManagerException) { throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable); } else { diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java index 14140b5fe264..3b676c65db75 100644 --- a/services/core/java/com/android/server/pm/AppsFilterBase.java +++ b/services/core/java/com/android/server/pm/AppsFilterBase.java @@ -28,7 +28,6 @@ import android.content.pm.SigningDetails; import android.os.Binder; import android.os.Handler; import android.os.Process; -import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; @@ -199,6 +198,7 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot { protected SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot; protected volatile boolean mCacheReady = false; + protected volatile boolean mCacheEnabled = true; protected static final boolean CACHE_VALID = true; protected static final boolean CACHE_INVALID = false; @@ -216,12 +216,12 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot { return mQueriesViaComponent.contains(callingAppId, targetAppId); } - protected boolean isImplicitlyQueryable(int callingAppId, int targetAppId) { - return mImplicitlyQueryable.contains(callingAppId, targetAppId); + protected boolean isImplicitlyQueryable(int callingUid, int targetUid) { + return mImplicitlyQueryable.contains(callingUid, targetUid); } - protected boolean isRetainedImplicitlyQueryable(int callingAppId, int targetAppId) { - return mRetainedImplicitlyQueryable.contains(callingAppId, targetAppId); + protected boolean isRetainedImplicitlyQueryable(int callingUid, int targetUid) { + return mRetainedImplicitlyQueryable.contains(callingUid, targetUid); } protected boolean isQueryableViaUsesLibrary(int callingAppId, int targetAppId) { @@ -337,13 +337,14 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot { || callingAppId == targetPkgSetting.getAppId()) { return false; } else if (Process.isSdkSandboxUid(callingAppId)) { + final int targetAppId = targetPkgSetting.getAppId(); + final int targetUid = UserHandle.getUid(userId, targetAppId); // we only allow sdk sandbox processes access to forcequeryable packages return !isForceQueryable(targetPkgSetting.getAppId()) - && !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId()); + && !isImplicitlyQueryable(callingUid, targetUid); } // use cache - if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache", - true)) { + if (mCacheReady && mCacheEnabled) { if (!shouldFilterApplicationUsingCache(callingUid, targetPkgSetting.getAppId(), userId)) { diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 79d72a3ad7f0..4c21195e2890 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; import android.os.Handler; +import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -223,6 +224,12 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, return new AppsFilterSnapshotImpl(AppsFilterImpl.this); } }; + readCacheEnabledSysProp(); + SystemProperties.addChangeCallback(this::readCacheEnabledSysProp); + } + + private void readCacheEnabledSysProp() { + mCacheEnabled = SystemProperties.getBoolean("debug.pm.use_app_filter_cache", true); } /** diff --git a/services/core/java/com/android/server/pm/AppsFilterLocked.java b/services/core/java/com/android/server/pm/AppsFilterLocked.java index 870f9da26210..29bb14e371c0 100644 --- a/services/core/java/com/android/server/pm/AppsFilterLocked.java +++ b/services/core/java/com/android/server/pm/AppsFilterLocked.java @@ -66,16 +66,16 @@ abstract class AppsFilterLocked extends AppsFilterBase { } @Override - protected boolean isImplicitlyQueryable(int callingAppId, int targetAppId) { + protected boolean isImplicitlyQueryable(int callingUid, int targetUid) { synchronized (mImplicitlyQueryableLock) { - return super.isImplicitlyQueryable(callingAppId, targetAppId); + return super.isImplicitlyQueryable(callingUid, targetUid); } } @Override - protected boolean isRetainedImplicitlyQueryable(int callingAppId, int targetAppId) { + protected boolean isRetainedImplicitlyQueryable(int callingUid, int targetUid) { synchronized (mImplicitlyQueryableLock) { - return super.isRetainedImplicitlyQueryable(callingAppId, targetAppId); + return super.isRetainedImplicitlyQueryable(callingUid, targetUid); } } diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java index 019c853471fe..4e268a277102 100644 --- a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java @@ -74,6 +74,7 @@ public final class AppsFilterSnapshotImpl extends AppsFilterBase { // cache is not ready, use an empty cache for the snapshot mShouldFilterCache = new WatchedSparseBooleanMatrix(); } + mCacheEnabled = orig.mCacheEnabled; mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>(); mBackgroundHandler = null; diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java index 797d4c3cfed3..f6472a739979 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAppsHelper.java @@ -33,11 +33,9 @@ import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS; import static com.android.server.pm.PackageManagerService.TAG; import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX; -import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.ApkLiteParseUtils; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; @@ -121,29 +119,6 @@ final class InitAppsHelper { mExecutorService = ParallelPackageParser.makeExecutorService(); } - private List<File> getFrameworkResApkSplitFiles() { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits"); - try { - final List<File> splits = new ArrayList<>(); - final List<ApexManager.ActiveApexInfo> activeApexInfos = - mPm.mApexManager.getActiveApexInfos(); - for (int i = 0; i < activeApexInfos.size(); i++) { - ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i); - File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits"); - if (splitsFolder.isDirectory()) { - for (File file : splitsFolder.listFiles()) { - if (ApkLiteParseUtils.isApkFile(file)) { - splits.add(file); - } - } - } - } - return splits; - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - } - private List<ScanPartition> getSystemScanPartitions() { final List<ScanPartition> scanPartitions = new ArrayList<>(); scanPartitions.addAll(mSystemPartitions); @@ -270,7 +245,7 @@ final class InitAppsHelper { long startTime) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); - scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0, + scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService); List<Runnable> unfinishedTasks = mExecutorService.shutdownNow(); @@ -338,15 +313,13 @@ final class InitAppsHelper { if (partition.getOverlayFolder() == null) { continue; } - scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null, + scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags, mSystemScanFlags | partition.scanFlag, packageParser, executorService); } - List<File> frameworkSplits = getFrameworkResApkSplitFiles(); - scanDirTracedLI(frameworkDir, frameworkSplits, - mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS, - mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, + scanDirTracedLI(frameworkDir, + mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, packageParser, executorService); if (!mPm.mPackages.containsKey("android")) { throw new IllegalStateException( @@ -356,12 +329,12 @@ final class InitAppsHelper { for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) { final ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.getPrivAppFolder() != null) { - scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null, + scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags, mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, packageParser, executorService); } - scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null, + scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags, mSystemScanFlags | partition.scanFlag, packageParser, executorService); } @@ -379,7 +352,7 @@ final class InitAppsHelper { } @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - private void scanDirTracedLI(File scanDir, List<File> frameworkSplits, + private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags, PackageParser2 packageParser, ExecutorService executorService) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); @@ -388,7 +361,7 @@ final class InitAppsHelper { // when scanning apk in apexes, we want to check the maxSdkVersion parseFlags |= PARSE_APK_IN_APEX; } - mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags, + mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags, packageParser, executorService); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index a25ee5e11e7e..1746d93c99b9 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3451,8 +3451,8 @@ final class InstallPackageHelper { if (throwable == null) { try { - AndroidPackage pkg = addForInitLI( - parseResult.parsedPackage, newParseFlags, newScanFlags, null); + addForInitLI(parseResult.parsedPackage, newParseFlags, newScanFlags, null); + AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal(); if (ai.isFactory && !ai.isActive) { disableSystemPackageLPw(pkg); } @@ -3472,7 +3472,7 @@ final class InstallPackageHelper { } @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags, + public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags, PackageParser2 packageParser, ExecutorService executorService) { final File[] files = scanDir.listFiles(); @@ -3486,7 +3486,7 @@ final class InstallPackageHelper { + " flags=0x" + Integer.toHexString(parseFlags)); } ParallelPackageParser parallelPackageParser = - new ParallelPackageParser(packageParser, executorService, frameworkSplits); + new ParallelPackageParser(packageParser, executorService); // Submit files for parsing in parallel int fileCount = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 39a6eb7bee8d..9481f8a7da87 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -210,6 +210,7 @@ import com.android.server.pm.permission.LegacyPermissionManagerService; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; @@ -222,6 +223,7 @@ import com.android.server.pm.pkg.mutate.PackageUserStateWrite; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.resolution.ComponentResolverApi; +import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; @@ -6539,7 +6541,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (dependentState == null) { continue; } - if (!Objects.equals(dependentState.getUserStateOrDefault(userId) + if (canSetOverlayPaths(dependentState.getUserStateOrDefault(userId) .getSharedLibraryOverlayPaths() .get(libName), newOverlayPaths)) { String dependentPackageName = dependent.getPackageName(); @@ -6563,7 +6565,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - outUpdatedPackageNames.add(targetPackageName); + if (canSetOverlayPaths(packageState.getUserStateOrDefault(userId).getOverlayPaths(), + newOverlayPaths)) { + outUpdatedPackageNames.add(targetPackageName); + } } commitPackageStateMutation(null, mutator -> { @@ -6614,6 +6619,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService invalidatePackageInfoCache(); } + private boolean canSetOverlayPaths(OverlayPaths origPaths, OverlayPaths newPaths) { + if (Objects.equals(origPaths, newPaths)) { + return false; + } + if ((origPaths == null && newPaths.isEmpty()) + || (newPaths == null && origPaths.isEmpty())) { + return false; + } + return true; + } + private void maybeUpdateSystemOverlays(String targetPackageName, OverlayPaths newOverlayPaths) { if (!mResolverReplaced) { if (targetPackageName.equals("android")) { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 4764a5c41db4..03f17bde8886 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -1208,11 +1208,23 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @NonNull + public PackageSetting addUsesLibraryInfo(@NonNull SharedLibraryInfo value) { + pkgState.addUsesLibraryInfo(value); + return this; + } + + @NonNull @Override public List<String> getUsesLibraryFiles() { return pkgState.getUsesLibraryFiles(); } + @NonNull + public PackageSetting addUsesLibraryFile(String value) { + pkgState.addUsesLibraryFile(value); + return this; + } + @Override public boolean isHiddenUntilInstalled() { return pkgState.isHiddenUntilInstalled(); diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java index 45030bfa59ce..56258844a3fe 100644 --- a/services/core/java/com/android/server/pm/ParallelPackageParser.java +++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java @@ -27,7 +27,6 @@ import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.ParsedPackage; import java.io.File; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -55,17 +54,9 @@ class ParallelPackageParser { private final ExecutorService mExecutorService; - private final List<File> mFrameworkSplits; - ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) { - this(packageParser, executorService, /* frameworkSplits= */ null); - } - - ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService, - List<File> frameworkSplits) { mPackageParser = packageParser; mExecutorService = executorService; - mFrameworkSplits = frameworkSplits; } static class ParseResult { @@ -134,6 +125,6 @@ class ParallelPackageParser { @VisibleForTesting protected ParsedPackage parsePackage(File scanFile, int parseFlags) throws PackageManagerException { - return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits); + return mPackageParser.parsePackage(scanFile, parseFlags, true); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 9037f042641f..80e9646282db 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5503,6 +5503,7 @@ public final class Settings implements Watchable, Snappable { + "set before trying to update the fingerprint."); } mFingerprints.put(userId, mExtendedFingerprint); + mPermissionUpgradeNeeded.put(userId, false); writeStateForUserAsync(userId); } } diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index df7e3755e463..c3eb2fdbf9b4 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -108,12 +108,12 @@ public final class SuspendPackageHelper { final SuspendParams newSuspendParams = new SuspendParams(dialogInfo, appExtras, launcherExtras); - final List<String> changedPackagesList = new ArrayList<>(packageNames.length); - final IntArray changedUids = new IntArray(packageNames.length); - final IntArray modifiedUids = new IntArray(packageNames.length); final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length); - ArraySet<String> modifiedPackages = new ArraySet<>(); + final List<String> notifyPackagesList = new ArrayList<>(packageNames.length); + final IntArray notifyUids = new IntArray(packageNames.length); + final ArraySet<String> changedPackagesList = new ArraySet<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); final boolean[] canSuspend = suspended ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid) : null; @@ -140,21 +140,17 @@ public final class SuspendPackageHelper { final WatchedArrayMap<String, SuspendParams> suspendParamsMap = packageState.getUserStateOrDefault(userId).getSuspendParams(); - if (suspended) { - if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) { - final SuspendParams suspendParams = suspendParamsMap.get(packageName); - // Skip if there's no changes - if (suspendParams != null - && Objects.equals(suspendParams.getDialogInfo(), dialogInfo) - && Objects.equals(suspendParams.getAppExtras(), appExtras) - && Objects.equals(suspendParams.getLauncherExtras(), - launcherExtras)) { - // Carried over API behavior, must notify change even if no change - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - continue; - } - } + + SuspendParams oldSuspendParams = suspendParamsMap == null + ? null : suspendParamsMap.get(packageName); + boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams); + + if (suspended && !changed) { + // Carried over API behavior, must notify change even if no change + notifyPackagesList.add(packageName); + notifyUids.add( + UserHandle.getUid(userId, packageState.getAppId())); + continue; } // If only the callingPackage is suspending this package, @@ -163,18 +159,21 @@ public final class SuspendPackageHelper { && CollectionUtils.size(suspendParamsMap) == 1 && suspendParamsMap.containsKey(callingPackage); if (suspended || packageUnsuspended) { + // Always notify of a suspend call + notify when fully unsuspended + notifyPackagesList.add(packageName); + notifyUids.add(UserHandle.getUid(userId, packageState.getAppId())); + } + + if (changed) { changedPackagesList.add(packageName); changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); } - - modifiedPackages.add(packageName); - modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId())); } mPm.commitPackageStateMutation(null, mutator -> { - final int size = modifiedPackages.size(); + final int size = changedPackagesList.size(); for (int index = 0; index < size; index++) { - final String packageName = modifiedPackages.valueAt(index); + final String packageName = changedPackagesList.valueAt(index); final PackageUserStateWrite userState = mutator.forPackage(packageName) .userState(userId); if (suspended) { @@ -185,19 +184,20 @@ public final class SuspendPackageHelper { } }); - if (!changedPackagesList.isEmpty()) { - final String[] changedPackages = changedPackagesList.toArray(new String[0]); + if (!notifyPackagesList.isEmpty()) { + final String[] changedPackages = + notifyPackagesList.toArray(new String[0]); sendPackagesSuspendedForUser( suspended ? Intent.ACTION_PACKAGES_SUSPENDED : Intent.ACTION_PACKAGES_UNSUSPENDED, - changedPackages, changedUids.toArray(), userId); + changedPackages, notifyUids.toArray(), userId); sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); mPm.scheduleWritePackageRestrictions(userId); } // Send the suspension changed broadcast to ensure suspension state is not stale. - if (!modifiedPackages.isEmpty()) { + if (!changedPackagesList.isEmpty()) { sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, - modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId); + changedPackagesList.toArray(new String[0]), changedUids.toArray(), userId); } return unmodifiablePackages.toArray(new String[0]); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 3b3e1db0f35d..c77459da8cec 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2048,8 +2048,8 @@ public class UserManagerService extends IUserManager.Stub { } } - @Override - public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { + @VisibleForTesting + boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, Resources.getSystem().getBoolean(com.android.internal @@ -2062,6 +2062,33 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, + @UserIdInt int mUserId) { + if (!isUserSwitcherEnabled(mUserId)) { + return false; + } + // The feature is enabled. But is it worth showing? + return showEvenIfNotActionable + || !hasUserRestriction(UserManager.DISALLOW_ADD_USER, mUserId) // Can add new user + || areThereMultipleSwitchableUsers(); // There are switchable users + } + + /** Returns true if there is more than one user that can be switched to. */ + private boolean areThereMultipleSwitchableUsers() { + List<UserInfo> aliveUsers = getUsers(true, true, true); + boolean isAnyAliveUser = false; + for (UserInfo userInfo : aliveUsers) { + if (userInfo.supportsSwitchToByUser()) { + if (isAnyAliveUser) { + return true; + } + isAnyAliveUser = true; + } + } + return false; + } + + @Override public boolean isRestricted(@UserIdInt int userId) { if (userId != UserHandle.getCallingUserId()) { checkCreateUsersPermission("query isRestricted for user " + userId); @@ -7072,4 +7099,5 @@ public class UserManagerService extends IUserManager.Stub { } return mAmInternal; } + } diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index 6caddaf430dc..f5ba3f61a00c 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -143,15 +143,6 @@ public class PackageParser2 implements AutoCloseable { @AnyThread public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) throws PackageManagerException { - return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null); - } - - /** - * TODO(b/135203078): Document new package parsing - */ - @AnyThread - public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches, - List<File> frameworkSplits) throws PackageManagerException { var files = packageFile.listFiles(); // Apk directory is directly nested under the current directory if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) { @@ -167,8 +158,7 @@ public class PackageParser2 implements AutoCloseable { long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; ParseInput input = mSharedResult.get().reset(); - ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags, - frameworkSplits); + ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); if (result.isError()) { throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), result.getException()); diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 70aca99f04e5..fe63deccec4b 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -385,16 +385,16 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Nullable @DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class) private Boolean requestRawExternalStorageAccess; - // TODO(chiuwinson): Non-null - @Nullable - private ArraySet<String> mimeGroups; + @NonNull + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class) + private Set<String> mimeGroups = emptySet(); // Usually there's code to set enabled to true during parsing, but it's possible to install // an APK targeting <R that doesn't contain an <application> tag. That code would be skipped // and never assign this, so initialize this to true for those cases. private long mBooleans = Booleans.ENABLED; private long mBooleans2; - @Nullable - private Set<String> mKnownActivityEmbeddingCerts; + @NonNull + private Set<String> mKnownActivityEmbeddingCerts = emptySet(); // Derived fields private long mLongVersionCode; private int mLocaleConfigRes; @@ -549,7 +549,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, if (mimeGroups != null && mimeGroups.size() > 500) { throw new IllegalStateException("Max limit on number of MIME Groups reached"); } - mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex)); + mimeGroups = CollectionUtils.add(mimeGroups, filter.getMimeGroup(groupIndex)); } } } @@ -935,8 +935,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @NonNull @Override public Set<String> getKnownActivityEmbeddingCerts() { - return mKnownActivityEmbeddingCerts == null ? Collections.emptySet() - : mKnownActivityEmbeddingCerts; + return mKnownActivityEmbeddingCerts; } @Override @@ -1949,8 +1948,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override - public ParsingPackage setKnownActivityEmbeddingCerts( - @Nullable Set<String> knownEmbeddingCerts) { + public ParsingPackage setKnownActivityEmbeddingCerts(@NonNull Set<String> knownEmbeddingCerts) { mKnownActivityEmbeddingCerts = knownEmbeddingCerts; return this; } @@ -2516,7 +2514,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, appInfo.setVersionCode(mLongVersionCode); appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess()); appInfo.setLocaleConfigRes(mLocaleConfigRes); - if (mKnownActivityEmbeddingCerts != null) { + if (!mKnownActivityEmbeddingCerts.isEmpty()) { appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts); } @@ -2593,11 +2591,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public AndroidPackageInternal hideAsFinal() { - // TODO(b/135203078): Lock as immutable if (mStorageUuid == null) { assignDerivedFields(); } assignDerivedFields2(); + makeImmutable(); return this; } @@ -2613,6 +2611,48 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, baseAppDataDir + Environment.DIR_USER_DE + systemUserSuffix); } + private void makeImmutable() { + usesLibraries = Collections.unmodifiableList(usesLibraries); + usesOptionalLibraries = Collections.unmodifiableList(usesOptionalLibraries); + usesNativeLibraries = Collections.unmodifiableList(usesNativeLibraries); + usesOptionalNativeLibraries = Collections.unmodifiableList(usesOptionalNativeLibraries); + originalPackages = Collections.unmodifiableList(originalPackages); + adoptPermissions = Collections.unmodifiableList(adoptPermissions); + requestedPermissions = Collections.unmodifiableList(requestedPermissions); + protectedBroadcasts = Collections.unmodifiableList(protectedBroadcasts); + apexSystemServices = Collections.unmodifiableList(apexSystemServices); + + activities = Collections.unmodifiableList(activities); + receivers = Collections.unmodifiableList(receivers); + services = Collections.unmodifiableList(services); + providers = Collections.unmodifiableList(providers); + permissions = Collections.unmodifiableList(permissions); + permissionGroups = Collections.unmodifiableList(permissionGroups); + instrumentations = Collections.unmodifiableList(instrumentations); + + overlayables = Collections.unmodifiableMap(overlayables); + libraryNames = Collections.unmodifiableList(libraryNames); + usesStaticLibraries = Collections.unmodifiableList(usesStaticLibraries); + usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries); + configPreferences = Collections.unmodifiableList(configPreferences); + reqFeatures = Collections.unmodifiableList(reqFeatures); + featureGroups = Collections.unmodifiableList(featureGroups); + usesPermissions = Collections.unmodifiableList(usesPermissions); + usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries); + implicitPermissions = Collections.unmodifiableList(implicitPermissions); + upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets); + keySetMapping = Collections.unmodifiableMap(keySetMapping); + attributions = Collections.unmodifiableList(attributions); + preferredActivityFilters = Collections.unmodifiableList(preferredActivityFilters); + processes = Collections.unmodifiableMap(processes); + mProperties = Collections.unmodifiableMap(mProperties); + queriesIntents = Collections.unmodifiableList(queriesIntents); + queriesPackages = Collections.unmodifiableList(queriesPackages); + queriesProviders = Collections.unmodifiableSet(queriesProviders); + mimeGroups = Collections.unmodifiableSet(mimeGroups); + mKnownActivityEmbeddingCerts = Collections.unmodifiableSet(mKnownActivityEmbeddingCerts); + } + @Override public long getLongVersionCode() { return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode); @@ -3041,7 +3081,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeIntArray(this.splitRevisionCodes); sForBoolean.parcel(this.resizeableActivity, dest, flags); dest.writeInt(this.autoRevokePermissions); - dest.writeArraySet(this.mimeGroups); + sForInternedStringSet.parcel(this.mimeGroups, dest, flags); dest.writeInt(this.gwpAsanMode); dest.writeSparseIntArray(this.minExtensionVersions); dest.writeMap(this.mProperties); @@ -3201,7 +3241,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.resizeableActivity = sForBoolean.unparcel(in); this.autoRevokePermissions = in.readInt(); - this.mimeGroups = (ArraySet<String>) in.readArraySet(boot); + this.mimeGroups = sForInternedStringSet.unparcel(in); this.gwpAsanMode = in.readInt(); this.minExtensionVersions = in.readSparseIntArray(); this.mProperties = in.readHashMap(boot); @@ -3224,6 +3264,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, assignDerivedFields(); assignDerivedFields2(); + + // Do not call makeImmutable here as cached parsing will need + // to mutate this instance before it's finalized. } @NonNull diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java index 8a82d6e41464..e07b77e7bc5b 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -18,7 +18,6 @@ package com.android.server.pm.pkg; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -59,19 +58,17 @@ import java.util.List; import java.util.Map; import java.util.Set; -/** - * Explicit interface used for consumers like mainline who need a {@link SystemApi @SystemApi} form - * of {@link AndroidPackage}. - * - * @hide - */ +/** @hide */ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) @Immutable public interface AndroidPackage { + // Methods below this comment are not yet exposed as API + /** * @see ApplicationInfo#areAttributionsUserVisible() * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible + * @hide */ @Nullable boolean areAttributionsUserVisible(); @@ -87,6 +84,7 @@ public interface AndroidPackage { * * @see ActivityInfo * @see PackageInfo#activities + * @hide */ @Immutable.Ignore @NonNull @@ -97,12 +95,14 @@ public interface AndroidPackage { * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * * @see R.styleable#AndroidManifestOriginalPackage_name + * @hide */ @NonNull List<String> getAdoptPermissions(); /** * @see R.styleable#AndroidManifestApexSystemService + * @hide */ @Immutable.Ignore @NonNull @@ -111,10 +111,12 @@ public interface AndroidPackage { /** * @see ApplicationInfo#appComponentFactory * @see R.styleable#AndroidManifestApplication_appComponentFactory + * @hide */ @Nullable String getAppComponentFactory(); + /** @hide */ @Immutable.Ignore @NonNull List<ParsedAttribution> getAttributions(); @@ -123,12 +125,14 @@ public interface AndroidPackage { * @see ApplicationInfo#AUTO_REVOKE_ALLOWED * @see ApplicationInfo#AUTO_REVOKE_DISCOURAGED * @see ApplicationInfo#AUTO_REVOKE_DISALLOWED + * @hide */ int getAutoRevokePermissions(); /** * @see ApplicationInfo#backupAgentName * @see R.styleable#AndroidManifestApplication_backupAgent + * @hide */ @Nullable String getBackupAgentName(); @@ -136,30 +140,35 @@ public interface AndroidPackage { /** * @see ApplicationInfo#banner * @see R.styleable#AndroidManifestApplication_banner + * @hide */ int getBanner(); /** * @see ApplicationInfo#sourceDir * @see ApplicationInfo#getBaseCodePath + * @hide */ @NonNull String getBaseApkPath(); /** * @see PackageInfo#baseRevisionCode + * @hide */ int getBaseRevisionCode(); /** * @see ApplicationInfo#category * @see R.styleable#AndroidManifestApplication_appCategory + * @hide */ int getCategory(); /** * @see ApplicationInfo#classLoaderName * @see R.styleable#AndroidManifestApplication_classLoader + * @hide */ @Nullable String getClassLoaderName(); @@ -167,6 +176,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#className * @see R.styleable#AndroidManifestApplication_name + * @hide */ @Nullable String getClassName(); @@ -174,18 +184,21 @@ public interface AndroidPackage { /** * @see ApplicationInfo#compatibleWidthLimitDp * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp + * @hide */ int getCompatibleWidthLimitDp(); /** * @see ApplicationInfo#compileSdkVersion * @see R.styleable#AndroidManifest_compileSdkVersion + * @hide */ int getCompileSdkVersion(); /** * @see ApplicationInfo#compileSdkVersionCodename * @see R.styleable#AndroidManifest_compileSdkVersionCodename + * @hide */ @Nullable String getCompileSdkVersionCodeName(); @@ -193,6 +206,7 @@ public interface AndroidPackage { /** * @see PackageInfo#configPreferences * @see R.styleable#AndroidManifestUsesConfiguration + * @hide */ @Immutable.Ignore @NonNull @@ -201,18 +215,21 @@ public interface AndroidPackage { /** * @see ApplicationInfo#dataExtractionRulesRes * @see R.styleable#AndroidManifestApplication_dataExtractionRules + * @hide */ int getDataExtractionRules(); /** * @see ApplicationInfo#descriptionRes * @see R.styleable#AndroidManifestApplication_description + * @hide */ int getDescriptionRes(); /** * @see PackageInfo#featureGroups * @see R.styleable#AndroidManifestUsesFeature + * @hide */ @Immutable.Ignore @NonNull @@ -221,12 +238,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#fullBackupContent * @see R.styleable#AndroidManifestApplication_fullBackupContent + * @hide */ int getFullBackupContent(); /** * @see ApplicationInfo#getGwpAsanMode() * @see R.styleable#AndroidManifestApplication_gwpAsanMode + * @hide */ @ApplicationInfo.GwpAsanMode int getGwpAsanMode(); @@ -234,12 +253,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#iconRes * @see R.styleable#AndroidManifestApplication_icon + * @hide */ int getIconRes(); /** * Permissions requested but not in the manifest. These may have been split or migrated from * previous versions/definitions. + * @hide */ @NonNull List<String> getImplicitPermissions(); @@ -247,12 +268,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#installLocation * @see R.styleable#AndroidManifest_installLocation + * @hide */ int getInstallLocation(); /** * @see InstrumentationInfo * @see PackageInfo#instrumentation + * @hide */ @Immutable.Ignore @NonNull @@ -264,6 +287,7 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey + * @hide */ @Immutable.Ignore @NonNull @@ -272,6 +296,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#mKnownActivityEmbeddingCerts * @see R.styleable#AndroidManifestApplication_knownActivityEmbeddingCerts + * @hide */ @SuppressWarnings("JavadocReference") @NonNull @@ -280,12 +305,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#labelRes * @see R.styleable#AndroidManifestApplication_label + * @hide */ int getLabelRes(); /** * @see ApplicationInfo#largestWidthLimitDp * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp + * @hide */ int getLargestWidthLimitDp(); @@ -293,6 +320,7 @@ public interface AndroidPackage { * Library names this package is declared as, for use by other packages with "uses-library". * * @see R.styleable#AndroidManifestLibrary + * @hide */ @NonNull List<String> getLibraryNames(); @@ -301,23 +329,27 @@ public interface AndroidPackage { * The resource ID used to provide the application's locales configuration. * * @see R.styleable#AndroidManifestApplication_localeConfig + * @hide */ int getLocaleConfigRes(); /** * @see ApplicationInfo#logo * @see R.styleable#AndroidManifestApplication_logo + * @hide */ int getLogo(); /** * @see PackageInfo#getLongVersionCode() + * @hide */ long getLongVersionCode(); /** * @see ApplicationInfo#manageSpaceActivityName * @see R.styleable#AndroidManifestApplication_manageSpaceActivity + * @hide */ @Nullable String getManageSpaceActivityName(); @@ -325,6 +357,7 @@ public interface AndroidPackage { /** * The package name as declared in the manifest, since the package can be renamed. For example, * static shared libs use synthetic package names. + * @hide */ @NonNull String getManifestPackageName(); @@ -332,39 +365,46 @@ public interface AndroidPackage { /** * @see ApplicationInfo#maxAspectRatio * @see R.styleable#AndroidManifestApplication_maxAspectRatio + * @hide */ float getMaxAspectRatio(); /** * @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion + * @hide */ int getMaxSdkVersion(); /** * @see ApplicationInfo#getMemtagMode() * @see R.styleable#AndroidManifestApplication_memtagMode + * @hide */ @ApplicationInfo.MemtagMode int getMemtagMode(); /** * TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?) + * @hide */ @Immutable.Ignore @Nullable Bundle getMetaData(); + /** @hide */ @Nullable Set<String> getMimeGroups(); /** * @see ApplicationInfo#minAspectRatio * @see R.styleable#AndroidManifestApplication_minAspectRatio + * @hide */ float getMinAspectRatio(); /** * @see R.styleable#AndroidManifestExtensionSdk + * @hide */ @Immutable.Ignore @Nullable @@ -373,24 +413,28 @@ public interface AndroidPackage { /** * @see ApplicationInfo#minSdkVersion * @see R.styleable#AndroidManifestUsesSdk_minSdkVersion + * @hide */ int getMinSdkVersion(); /** * @see ApplicationInfo#getNativeHeapZeroInitialized() * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized + * @hide */ @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized(); /** * @see ApplicationInfo#nativeLibraryDir + * @hide */ @Nullable String getNativeLibraryDir(); /** * @see ApplicationInfo#nativeLibraryRootDir + * @hide */ @Nullable String getNativeLibraryRootDir(); @@ -398,6 +442,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#networkSecurityConfigRes * @see R.styleable#AndroidManifestApplication_networkSecurityConfig + * @hide */ int getNetworkSecurityConfigRes(); @@ -407,6 +452,7 @@ public interface AndroidPackage { * * @see ApplicationInfo#nonLocalizedLabel * @see R.styleable#AndroidManifestApplication_label + * @hide */ @Nullable CharSequence getNonLocalizedLabel(); @@ -416,6 +462,7 @@ public interface AndroidPackage { * available. * * @see R.styleable#AndroidManifestOriginalPackage} + * @hide */ @NonNull List<String> getOriginalPackages(); @@ -423,6 +470,7 @@ public interface AndroidPackage { /** * @see PackageInfo#overlayCategory * @see R.styleable#AndroidManifestResourceOverlay_category + * @hide */ @Nullable String getOverlayCategory(); @@ -430,12 +478,14 @@ public interface AndroidPackage { /** * @see PackageInfo#overlayPriority * @see R.styleable#AndroidManifestResourceOverlay_priority + * @hide */ int getOverlayPriority(); /** * @see PackageInfo#overlayTarget * @see R.styleable#AndroidManifestResourceOverlay_targetPackage + * @hide */ @Nullable String getOverlayTarget(); @@ -443,24 +493,28 @@ public interface AndroidPackage { /** * @see PackageInfo#targetOverlayableName * @see R.styleable#AndroidManifestResourceOverlay_targetName + * @hide */ @Nullable String getOverlayTargetOverlayableName(); /** * Map of overlayable name to actor name. + * @hide */ @NonNull Map<String, String> getOverlayables(); /** * @see PackageInfo#packageName + * @hide */ String getPackageName(); /** * @see ApplicationInfo#scanSourceDir * @see ApplicationInfo#getCodePath + * @hide */ @NonNull String getPath(); @@ -468,12 +522,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#permission * @see R.styleable#AndroidManifestApplication_permission + * @hide */ @Nullable String getPermission(); /** * @see android.content.pm.PermissionGroupInfo + * @hide */ @Immutable.Ignore @NonNull @@ -482,6 +538,7 @@ public interface AndroidPackage { /** * @see PermissionInfo * @see PackageInfo#permissions + * @hide */ @Immutable.Ignore @NonNull @@ -492,6 +549,7 @@ public interface AndroidPackage { * <p> * Map of component className to intent info inside that component. TODO(b/135203078): Is this * actually used/working? + * @hide */ @Immutable.Ignore @NonNull @@ -500,12 +558,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#processName * @see R.styleable#AndroidManifestApplication_process + * @hide */ @NonNull String getProcessName(); /** * @see android.content.pm.ProcessInfo + * @hide */ @Immutable.Ignore @NonNull @@ -513,6 +573,7 @@ public interface AndroidPackage { /** * Returns the properties set on the application + * @hide */ @Immutable.Ignore @NonNull @@ -522,6 +583,7 @@ public interface AndroidPackage { * System protected broadcasts. * * @see R.styleable#AndroidManifestProtectedBroadcast + * @hide */ @NonNull List<String> getProtectedBroadcasts(); @@ -537,6 +599,7 @@ public interface AndroidPackage { * * @see ProviderInfo * @see PackageInfo#providers + * @hide */ @Immutable.Ignore @NonNull @@ -546,6 +609,7 @@ public interface AndroidPackage { * Intents that this package may query or require and thus requires visibility into. * * @see R.styleable#AndroidManifestQueriesIntent + * @hide */ @Immutable.Ignore @NonNull @@ -555,6 +619,7 @@ public interface AndroidPackage { * Other packages that this package may query or require and thus requires visibility into. * * @see R.styleable#AndroidManifestQueriesPackage + * @hide */ @NonNull List<String> getQueriesPackages(); @@ -563,6 +628,7 @@ public interface AndroidPackage { * Authorities that this package may query or require and thus requires visibility into. * * @see R.styleable#AndroidManifestQueriesProvider + * @hide */ @NonNull Set<String> getQueriesProviders(); @@ -583,6 +649,7 @@ public interface AndroidPackage { * * @see ActivityInfo * @see PackageInfo#receivers + * @hide */ @Immutable.Ignore @NonNull @@ -591,6 +658,7 @@ public interface AndroidPackage { /** * @see PackageInfo#reqFeatures * @see R.styleable#AndroidManifestUsesFeature + * @hide */ @Immutable.Ignore @NonNull @@ -603,6 +671,7 @@ public interface AndroidPackage { * * @see PackageInfo#requestedPermissions * @see R.styleable#AndroidManifestUsesPermission + * @hide */ @NonNull List<String> getRequestedPermissions(); @@ -610,6 +679,7 @@ public interface AndroidPackage { /** * @see PackageInfo#requiredAccountType * @see R.styleable#AndroidManifestApplication_requiredAccountType + * @hide */ @Nullable String getRequiredAccountType(); @@ -617,6 +687,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#requiresSmallestWidthDp * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp + * @hide */ int getRequiresSmallestWidthDp(); @@ -626,6 +697,7 @@ public interface AndroidPackage { * * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE + * @hide */ @Nullable Boolean getResizeableActivity(); @@ -634,6 +706,7 @@ public interface AndroidPackage { * SHA-512 hash of the only APK that can be used to update a system package. * * @see R.styleable#AndroidManifestRestrictUpdate + * @hide */ @Immutable.Ignore @Nullable @@ -644,6 +717,7 @@ public interface AndroidPackage { * * @see PackageInfo#restrictedAccountType * @see R.styleable#AndroidManifestApplication_restrictedAccountType + * @hide */ @Nullable String getRestrictedAccountType(); @@ -651,22 +725,26 @@ public interface AndroidPackage { /** * @see ApplicationInfo#roundIconRes * @see R.styleable#AndroidManifestApplication_roundIcon + * @hide */ int getRoundIconRes(); /** * @see R.styleable#AndroidManifestSdkLibrary_name + * @hide */ @Nullable String getSdkLibName(); /** * @see R.styleable#AndroidManifestSdkLibrary_versionMajor + * @hide */ int getSdkLibVersionMajor(); /** * @see ApplicationInfo#secondaryNativeLibraryDir + * @hide */ @Nullable String getSecondaryNativeLibraryDir(); @@ -682,6 +760,7 @@ public interface AndroidPackage { * * @see ServiceInfo * @see PackageInfo#services + * @hide */ @Immutable.Ignore @NonNull @@ -690,6 +769,7 @@ public interface AndroidPackage { /** * @see PackageInfo#sharedUserId * @see R.styleable#AndroidManifest_sharedUserId + * @hide */ @Nullable String getSharedUserId(); @@ -697,12 +777,14 @@ public interface AndroidPackage { /** * @see PackageInfo#sharedUserLabel * @see R.styleable#AndroidManifest_sharedUserLabel + * @hide */ int getSharedUserLabel(); /** * The signature data of all APKs in this package, which must be exactly the same across the * base and splits. + * @hide */ @Immutable.Ignore @NonNull @@ -711,6 +793,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#splitClassLoaderNames * @see R.styleable#AndroidManifestApplication_classLoader + * @hide */ @Immutable.Ignore @Nullable @@ -719,6 +802,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#splitSourceDirs * @see ApplicationInfo#getSplitCodePaths + * @hide */ @Immutable.Ignore @NonNull @@ -726,6 +810,7 @@ public interface AndroidPackage { /** * @see ApplicationInfo#splitDependencies + * @hide */ @Immutable.Ignore @NonNull @@ -733,6 +818,7 @@ public interface AndroidPackage { /** * Flags of any split APKs; ordered by parsed splitName + * @hide */ @Immutable.Ignore @Nullable @@ -743,6 +829,7 @@ public interface AndroidPackage { * * @see ApplicationInfo#splitNames * @see PackageInfo#splitNames + * @hide */ @Immutable.Ignore @NonNull @@ -750,6 +837,7 @@ public interface AndroidPackage { /** * @see PackageInfo#splitRevisionCodes + * @hide */ @Immutable.Ignore @NonNull @@ -757,30 +845,35 @@ public interface AndroidPackage { /** * @see R.styleable#AndroidManifestStaticLibrary_name + * @hide */ @Nullable String getStaticSharedLibName(); /** * @see R.styleable#AndroidManifestStaticLibrary_version + * @hide */ long getStaticSharedLibVersion(); /** * @see ApplicationInfo#targetSandboxVersion * @see R.styleable#AndroidManifest_targetSandboxVersion + * @hide */ int getTargetSandboxVersion(); /** * @see ApplicationInfo#targetSdkVersion * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion + * @hide */ int getTargetSdkVersion(); /** * @see ApplicationInfo#taskAffinity * @see R.styleable#AndroidManifestApplication_taskAffinity + * @hide */ @Nullable String getTaskAffinity(); @@ -788,12 +881,14 @@ public interface AndroidPackage { /** * @see ApplicationInfo#theme * @see R.styleable#AndroidManifestApplication_theme + * @hide */ int getTheme(); /** * @see ApplicationInfo#uiOptions * @see R.styleable#AndroidManifestApplication_uiOptions + * @hide */ int getUiOptions(); @@ -802,6 +897,7 @@ public interface AndroidPackage { * {@link android.os.UserHandle#SYSTEM}. * * @deprecated Use {@link PackageState#getAppId()} instead. + * @hide */ @Deprecated int getUid(); @@ -811,18 +907,21 @@ public interface AndroidPackage { * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestUpgradeKeySet + * @hide */ @NonNull Set<String> getUpgradeKeySets(); /** * @see R.styleable#AndroidManifestUsesLibrary + * @hide */ @NonNull List<String> getUsesLibraries(); /** * @see R.styleable#AndroidManifestUsesNativeLibrary + * @hide */ @NonNull List<String> getUsesNativeLibraries(); @@ -833,6 +932,7 @@ public interface AndroidPackage { * absence manually. * * @see R.styleable#AndroidManifestUsesLibrary + * @hide */ @NonNull List<String> getUsesOptionalLibraries(); @@ -843,10 +943,12 @@ public interface AndroidPackage { * handle absence manually. * * @see R.styleable#AndroidManifestUsesNativeLibrary + * @hide */ @NonNull List<String> getUsesOptionalNativeLibraries(); + /** @hide */ @Immutable.Ignore @NonNull List<ParsedUsesPermission> getUsesPermissions(); @@ -855,12 +957,14 @@ public interface AndroidPackage { * TODO(b/135203078): Move SDK library stuff to an inner data class * * @see R.styleable#AndroidManifestUsesSdkLibrary + * @hide */ @NonNull List<String> getUsesSdkLibraries(); /** * @see R.styleable#AndroidManifestUsesSdkLibrary_certDigest + * @hide */ @Immutable.Ignore @Nullable @@ -868,6 +972,7 @@ public interface AndroidPackage { /** * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor + * @hide */ @Immutable.Ignore @Nullable @@ -877,12 +982,14 @@ public interface AndroidPackage { * TODO(b/135203078): Move static library stuff to an inner data class * * @see R.styleable#AndroidManifestUsesStaticLibrary + * @hide */ @NonNull List<String> getUsesStaticLibraries(); /** * @see R.styleable#AndroidManifestUsesStaticLibrary_certDigest + * @hide */ @Immutable.Ignore @Nullable @@ -890,6 +997,7 @@ public interface AndroidPackage { /** * @see R.styleable#AndroidManifestUsesStaticLibrary_version + * @hide */ @Immutable.Ignore @Nullable @@ -897,60 +1005,72 @@ public interface AndroidPackage { /** * @see PackageInfo#versionName + * @hide */ @Nullable String getVersionName(); /** * @see ApplicationInfo#volumeUuid + * @hide */ @Nullable String getVolumeUuid(); + /** @hide */ @Nullable String getZygotePreloadName(); + /** @hide */ boolean hasPreserveLegacyExternalStorage(); /** * @see ApplicationInfo#PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION * @see R.styleable#AndroidManifestApplication_requestForegroundServiceExemption + * @hide */ boolean hasRequestForegroundServiceExemption(); /** * @see ApplicationInfo#getRequestRawExternalStorageAccess() * @see R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess + * @hide */ Boolean hasRequestRawExternalStorageAccess(); /** * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE + * @hide */ boolean isAllowAudioPlaybackCapture(); /** * @see ApplicationInfo#FLAG_ALLOW_BACKUP + * @hide */ boolean isAllowBackup(); /** * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA + * @hide */ boolean isAllowClearUserData(); /** * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE + * @hide */ boolean isAllowClearUserDataOnFailedRestore(); /** * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING + * @hide */ boolean isAllowNativeHeapPointerTagging(); /** * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING + * @hide */ boolean isAllowTaskReparenting(); @@ -960,115 +1080,138 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_anyDensity * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES + * @hide */ boolean isAnyDensity(); + /** @hide */ boolean isApex(); /** * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND + * @hide */ boolean isBackupInForeground(); /** * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED + * @hide */ boolean isBaseHardwareAccelerated(); /** * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE + * @hide */ boolean isCantSaveState(); /** * @see PackageInfo#coreApp + * @hide */ boolean isCoreApp(); /** * @see ApplicationInfo#crossProfile + * @hide */ boolean isCrossProfile(); /** * @see ApplicationInfo#FLAG_DEBUGGABLE + * @hide */ boolean isDebuggable(); /** * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE + * @hide */ boolean isDefaultToDeviceProtectedStorage(); /** * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE + * @hide */ boolean isDirectBootAware(); /** * @see ApplicationInfo#enabled * @see R.styleable#AndroidManifestApplication_enabled + * @hide */ boolean isEnabled(); /** * @see ApplicationInfo#FLAG_EXTERNAL_STORAGE + * @hide */ boolean isExternalStorage(); /** * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS + * @hide */ boolean isExtractNativeLibs(); /** * @see ApplicationInfo#FLAG_FACTORY_TEST + * @hide */ boolean isFactoryTest(); /** * @see R.styleable#AndroidManifestApplication_forceQueryable + * @hide */ boolean isForceQueryable(); /** * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY + * @hide */ boolean isFullBackupOnly(); /** * @see ApplicationInfo#FLAG_IS_GAME + * @hide */ @Deprecated boolean isGame(); /** * @see ApplicationInfo#FLAG_HAS_CODE + * @hide */ boolean isHasCode(); /** * @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS + * @hide */ boolean isHasDomainUrls(); /** * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA + * @hide */ boolean isHasFragileUserData(); /** * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING + * @hide */ boolean isIsolatedSplitLoading(); /** * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE + * @hide */ boolean isKillAfterRestore(); /** * @see ApplicationInfo#FLAG_LARGE_HEAP + * @hide */ boolean isLargeHeap(); @@ -1077,83 +1220,99 @@ public interface AndroidPackage { * smaller than the current SDK version. * * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion + * @hide */ boolean isLeavingSharedUid(); /** * @see ApplicationInfo#FLAG_MULTIARCH + * @hide */ boolean isMultiArch(); /** * @see ApplicationInfo#nativeLibraryRootRequiresIsa + * @hide */ boolean isNativeLibraryRootRequiresIsa(); /** * @see ApplicationInfo#PRIVATE_FLAG_ODM + * @hide */ boolean isOdm(); /** * @see ApplicationInfo#PRIVATE_FLAG_OEM + * @hide */ boolean isOem(); /** * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback + * @hide */ boolean isOnBackInvokedCallbackEnabled(); /** * @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY * @see ApplicationInfo#isResourceOverlay() + * @hide */ boolean isOverlay(); /** * @see PackageInfo#mOverlayIsStatic + * @hide */ boolean isOverlayIsStatic(); /** * @see ApplicationInfo#PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE + * @hide */ boolean isPartiallyDirectBootAware(); /** * @see ApplicationInfo#FLAG_PERSISTENT + * @hide */ boolean isPersistent(); /** * @see ApplicationInfo#PRIVATE_FLAG_PRIVILEGED + * @hide */ boolean isPrivileged(); /** * @see ApplicationInfo#PRIVATE_FLAG_PRODUCT + * @hide */ boolean isProduct(); /** * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE + * @hide */ boolean isProfileable(); /** * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL + * @hide */ boolean isProfileableByShell(); /** * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE + * @hide */ boolean isRequestLegacyExternalStorage(); /** * @see PackageInfo#requiredForAllUsers * @see R.styleable#AndroidManifestApplication_requiredForAllUsers + * @hide */ boolean isRequiredForAllUsers(); @@ -1162,6 +1321,7 @@ public interface AndroidPackage { * when the application's user data is cleared. * * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared + * @hide */ boolean isResetEnabledSettingsOnAppDataCleared(); @@ -1171,36 +1331,43 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_resizeable * @see ApplicationInfo#FLAG_RESIZEABLE_FOR_SCREENS + * @hide */ boolean isResizeable(); /** * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION + * @hide */ boolean isResizeableActivityViaSdkVersion(); /** * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION + * @hide */ boolean isRestoreAnyVersion(); /** * True means that this package/app contains an SDK library. + * @hide */ boolean isSdkLibrary(); /** * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY + * @hide */ boolean isSignedWithPlatformKey(); /** * @see ApplicationInfo#PRIVATE_FLAG_STATIC_SHARED_LIBRARY + * @hide */ boolean isStaticSharedLibrary(); /** * @see PackageInfo#isStub + * @hide */ boolean isStub(); @@ -1210,6 +1377,7 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS + * @hide */ boolean isSupportsExtraLargeScreens(); @@ -1219,6 +1387,7 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_largeScreens * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS + * @hide */ boolean isSupportsLargeScreens(); @@ -1227,11 +1396,13 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_normalScreens * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS + * @hide */ boolean isSupportsNormalScreens(); /** * @see ApplicationInfo#FLAG_SUPPORTS_RTL + * @hide */ boolean isSupportsRtl(); @@ -1241,21 +1412,25 @@ public interface AndroidPackage { * * @see R.styleable#AndroidManifestSupportsScreens_smallScreens * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS + * @hide */ boolean isSupportsSmallScreens(); /** * @see ApplicationInfo#FLAG_SYSTEM + * @hide */ boolean isSystem(); /** * @see ApplicationInfo#PRIVATE_FLAG_SYSTEM_EXT + * @hide */ boolean isSystemExt(); /** * @see ApplicationInfo#FLAG_TEST_ONLY + * @hide */ boolean isTestOnly(); @@ -1265,26 +1440,31 @@ public interface AndroidPackage { * cpuAbiOverride is also set. * * @see R.attr#use32bitAbi + * @hide */ boolean isUse32BitAbi(); /** * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX + * @hide */ boolean isUseEmbeddedDex(); /** * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC + * @hide */ boolean isUsesCleartextTraffic(); /** * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API + * @hide */ boolean isUsesNonSdkApi(); /** * @see ApplicationInfo#PRIVATE_FLAG_VENDOR + * @hide */ boolean isVendor(); @@ -1294,11 +1474,13 @@ public interface AndroidPackage { * @see R.styleable#AndroidManifestActivity_visibleToInstantApps * @see R.styleable#AndroidManifestProvider_visibleToInstantApps * @see R.styleable#AndroidManifestService_visibleToInstantApps + * @hide */ boolean isVisibleToInstantApps(); /** * @see ApplicationInfo#FLAG_VM_SAFE_MODE + * @hide */ boolean isVmSafeMode(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 12e9671f301a..c0e063de5780 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -21,37 +21,20 @@ import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UserIdInt; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningInfo; import android.processor.immutability.Immutable; import android.util.SparseArray; -import com.android.server.pm.PackageSetting; -import com.android.server.pm.Settings; - import java.io.File; import java.util.List; import java.util.Map; import java.util.Set; /** - * The API surface for a {@link PackageSetting}. Methods are expected to return immutable objects. - * This may mean copying data on each invocation until related classes are refactored to be - * immutable. - * <p> - * Note that until immutability or read-only caching is enabled, {@link PackageSetting} cannot be - * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data. This is a - * relatively expensive operation since it has to create an object for every package, but it's much - * lighter than the alternative of generating {@link PackageInfo} objects. - * <p> - * TODO: Documentation TODO: Currently missing, should be exposed as API? - * <ul> - * <li>keySetData</li> - * <li>installSource</li> - * <li>incrementalStates</li> - * </ul> + * The exposed system server process API for package data, shared with the internal + * PackageManagerService implementation. All returned data is guaranteed immutable. * * @hide */ @@ -59,28 +42,61 @@ import java.util.Set; @Immutable public interface PackageState { + // Methods below this comment are not yet exposed as API + + /* + * Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be + * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data. + * This is a relatively expensive operation since it has to create an object for every package, + * but it's much lighter than the alternative of generating {@link PackageInfo} objects. + * <p> + * TODO: Documentation + * TODO: Currently missing, should be exposed as API? + * - keySetData + * - installSource + * - incrementalStates + */ + + // Non-doc comment invisible to API consumers: + // Guidelines: + // - All return values should prefer non-null, immutable interfaces with only exposed getters + // - Unless null itself communicates something important + // - If the type is a Java collection type, it must be wrapped with unmodifiable + // - All type names must be non-suffixed, with any internal types being refactored to suffix + // with _Internal as necessary + // - No exposure of raw values that are overridden during parsing, such as CPU ABI + // - Mirroring another available system or public API is not enough justification to violate + // these guidelines + /** * This can be null whenever a physical APK on device is missing. This can be the result of * removing an external storage device where the APK resides. - * <p> - * This will result in the system reading the {@link PackageSetting} from disk, but without - * being able to parse the base APK's AndroidManifest.xml to read all of its metadata. The data - * that is written and read in {@link Settings} includes a minimal set of metadata needed to - * perform other checks in the system. - * <p> + * <p/> + * This will result in the system reading the state from disk, but without being able to parse + * the base APK's AndroidManifest.xml to read all of its metadata. The only available data that + * is written and read is the minimal set required to perform other checks in the system. + * <p/> * This is important in order to enforce uniqueness within the system, as the package, even if * on a removed storage device, is still considered installed. Another package of the same * application ID or declaring the same permissions or similar cannot be installed. - * <p> + * <p/> * Re-attaching the storage device to make the APK available should allow the user to use the * app once the device reboots or otherwise re-scans it. + * <p/> + * This can also occur in an device OTA situation where the package is no longer parseable on + * an updated SDK version, causing it to be rejectd, but the state associated with it retained, + * similarly to if the package had been uninstalled with the --keep-data option. + * + * @hide */ @Nullable AndroidPackage getAndroidPackage(); /** * The non-user-specific UID, or the UID if the user ID is - * {@link android.os.UserHandle#USER_SYSTEM}. + * {@link android.os.UserHandle#SYSTEM}. + * + * @hide */ int getAppId(); @@ -89,6 +105,7 @@ public interface PackageState { * applied if the application itself does not declare a category. * * @see AndroidPackage#getCategory() + * @hide */ int getCategoryOverride(); @@ -96,6 +113,8 @@ public interface PackageState { * The install time CPU override, if any. This value is written at install time * and doesn't change during the life of an install. If non-null, * {@link #getPrimaryCpuAbi()} will also contain the same value. + * + * @hide */ @Nullable String getCpuAbiOverride(); @@ -103,6 +122,8 @@ public interface PackageState { /** * In epoch milliseconds. The last modified time of the file directory which houses the app * APKs. Only updated on package update; does not track realtime modifications. + * + * @hide */ long getLastModifiedTime(); @@ -110,6 +131,8 @@ public interface PackageState { * An aggregation across the framework of the last time an app was used for a particular reason. * Keys are indexes into the array represented by {@link PackageManager.NotifyReason}, values * are in epoch milliseconds. + * + * @hide */ @Immutable.Ignore @Size(PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT) @@ -119,12 +142,15 @@ public interface PackageState { /** * In epoch milliseconds. The timestamp of the last time the package on device went through * an update package installation. + * + * @hide */ long getLastUpdateTime(); /** * Cached here in case the physical code directory on device is unmounted. * @see AndroidPackage#getLongVersionCode() + * @hide */ long getVersionCode(); @@ -132,37 +158,43 @@ public interface PackageState { * Maps mime group name to the set of Mime types in a group. Mime groups declared by app are * populated with empty sets at construction. Mime groups can not be created/removed at runtime, * thus keys in this map should not change. + * + * @hide */ @NonNull Map<String, Set<String>> getMimeGroups(); /** * @see AndroidPackage#getPackageName() + * @hide */ @NonNull String getPackageName(); /** - * TODO: Rename this to getCodePath * @see AndroidPackage#getPath() + * @hide */ @NonNull File getPath(); /** * @see ApplicationInfo#primaryCpuAbi + * @hide */ @Nullable String getPrimaryCpuAbi(); /** * @see ApplicationInfo#secondaryCpuAbi + * @hide */ @Nullable String getSecondaryCpuAbi(); /** * Whether the package shares the same user ID as other packages + * @hide */ boolean hasSharedUser(); @@ -172,13 +204,16 @@ public interface PackageState { * * @return the app ID of the shared user that this package is a part of, or -1 if it's not part * of a shared user. + * @hide */ int getSharedUserAppId(); + /** @hide */ @Immutable.Ignore @NonNull SigningInfo getSigningInfo(); + /** @hide */ @Immutable.Ignore @NonNull SparseArray<? extends PackageUserState> getUserStates(); @@ -186,6 +221,7 @@ public interface PackageState { /** * @return the result of {@link #getUserStates()}.get(userId) or * {@link PackageUserState#DEFAULT} if the state doesn't exist. + * @hide */ @NonNull default PackageUserState getUserStateOrDefault(@UserIdInt int userId) { @@ -197,12 +233,14 @@ public interface PackageState { * The actual files resolved for each shared library. * * @see R.styleable#AndroidManifestUsesLibrary + * @hide */ @NonNull List<String> getUsesLibraryFiles(); /** * @see R.styleable#AndroidManifestUsesLibrary + * @hide */ @Immutable.Ignore @NonNull @@ -210,6 +248,7 @@ public interface PackageState { /** * @see R.styleable#AndroidManifestUsesSdkLibrary + * @hide */ @Immutable.Ignore @NonNull @@ -217,6 +256,7 @@ public interface PackageState { /** * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor + * @hide */ @Immutable.Ignore @NonNull @@ -224,6 +264,7 @@ public interface PackageState { /** * @see R.styleable#AndroidManifestUsesStaticLibrary + * @hide */ @Immutable.Ignore @NonNull @@ -231,6 +272,7 @@ public interface PackageState { /** * @see R.styleable#AndroidManifestUsesStaticLibrary_version + * @hide */ @Immutable.Ignore @NonNull @@ -238,18 +280,22 @@ public interface PackageState { /** * @see AndroidPackage#getVolumeUuid() + * @hide */ @Nullable String getVolumeUuid(); /** * @see AndroidPackage#isExternalStorage() + * @hide */ boolean isExternalStorage(); /** * Whether a package was installed --force-queryable such that it is always queryable by any * package, regardless of their manifest content. + * + * @hide */ boolean isForceQueryableOverride(); @@ -258,67 +304,82 @@ public interface PackageState { * * @see PackageManager#MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS * @see PackageManager#setSystemAppState + * @hide */ boolean isHiddenUntilInstalled(); /** * @see com.android.server.pm.permission.UserPermissionState + * @hide */ boolean isInstallPermissionsFixed(); /** * @see AndroidPackage#isOdm() + * @hide */ boolean isOdm(); /** * @see AndroidPackage#isOem() + * @hide */ boolean isOem(); /** * @see AndroidPackage#isPrivileged() + * @hide */ boolean isPrivileged(); /** * @see AndroidPackage#isProduct() + * @hide */ boolean isProduct(); /** * @see ApplicationInfo#PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER + * @hide */ boolean isRequiredForSystemUser(); /** * @see AndroidPackage#isSystem() + * @hide */ boolean isSystem(); /** * @see AndroidPackage#isSystemExt() + * @hide */ boolean isSystemExt(); /** * Whether or not an update is available. Ostensibly only for instant apps. + * @hide */ boolean isUpdateAvailable(); /** * Whether this app is on the /data partition having been upgraded from a preinstalled app on a * system partition. + * + * @hide */ boolean isUpdatedSystemApp(); /** * Whether this app is packaged in an updated apex. + * + * @hide */ boolean isApkInUpdatedApex(); /** * @see AndroidPackage#isVendor() + * @hide */ boolean isVendor(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index eac084228b81..28309c77f8cf 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -33,6 +33,7 @@ import com.android.server.pm.PackageSetting; import com.android.server.pm.Settings; import java.io.File; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -170,7 +171,7 @@ public class PackageStateImpl implements PackageState { mLastModifiedTime = pkgState.getLastModifiedTime(); mLastUpdateTime = pkgState.getLastUpdateTime(); mLongVersionCode = pkgState.getVersionCode(); - mMimeGroups = pkgState.getMimeGroups(); + mMimeGroups = Collections.unmodifiableMap(pkgState.getMimeGroups()); mPath = pkgState.getPath(); mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); @@ -180,8 +181,8 @@ public class PackageStateImpl implements PackageState { mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor(); mUsesStaticLibraries = pkgState.getUsesStaticLibraries(); mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions(); - mUsesLibraryInfos = pkgState.getUsesLibraryInfos(); - mUsesLibraryFiles = pkgState.getUsesLibraryFiles(); + mUsesLibraryInfos = Collections.unmodifiableList(pkgState.getUsesLibraryInfos()); + mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles()); setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride()); setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled()); setBoolean(Booleans.INSTALL_PERMISSIONS_FIXED, pkgState.isInstallPermissionsFixed()); @@ -195,8 +196,8 @@ public class PackageStateImpl implements PackageState { int userStatesSize = userStates.size(); mUserStates = new SparseArray<>(userStatesSize); for (int index = 0; index < userStatesSize; index++) { - mUserStates.put(mUserStates.keyAt(index), - UserStateImpl.copy(mUserStates.valueAt(index))); + mUserStates.put(userStates.keyAt(index), + UserStateImpl.copy(userStates.valueAt(index))); } } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java index 84799ea29423..8f5795bc47eb 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java @@ -31,6 +31,7 @@ import java.util.UUID; /** * Exposes internal types for internal usage of {@link PackageState}. + * @hide */ public interface PackageStateInternal extends PackageState { diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java index 2c8b977b2bd6..1ae00d3c31f5 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.server.pm.PackageSetting; @@ -38,6 +39,7 @@ import java.util.stream.Collectors; * * These fields are also not copied into any cloned PackageSetting, to preserve the old behavior * where they would be lost implicitly by re-generating the package object. + * @hide */ @DataClass(genSetters = true, genConstructor = false, genBuilder = false) @DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting"}) @@ -69,6 +71,18 @@ public class PackageStateUnserialized { mPackageSetting = packageSetting; } + @NonNull + public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryInfo value) { + usesLibraryInfos = CollectionUtils.add(usesLibraryInfos, value); + return this; + } + + @NonNull + public PackageStateUnserialized addUsesLibraryFile(@NonNull String value) { + usesLibraryFiles = CollectionUtils.add(usesLibraryFiles, value); + return this; + } + private long[] lazyInitLastPackageUsageTimeInMills() { return new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT]; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index a883a059de0a..9749cfa6d82b 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -24,6 +24,7 @@ import android.util.SparseArray; import com.android.server.pm.pkg.component.ParsedMainComponent; +/** @hide */ public class PackageStateUtils { public static boolean isMatch(PackageState packageState, long flags) { diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2d2538553bb6..a1b6f1d69751 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -26,9 +26,8 @@ import android.util.ArraySet; import java.util.Map; + /** - * The API surface for {@link PackageUserStateInternal}, for use by in-process mainline consumers. - * * The parent of this class is {@link PackageState}, which handles non-user state, exposing this * interface for per-user state. * @@ -36,123 +35,164 @@ import java.util.Map; */ // TODO(b/173807334): Expose API //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) -@Immutable.Ignore(reason = "Exposed through PackageState pending refactor") +@Immutable public interface PackageUserState { - /** @hide */ + /** + * @hide + */ @NonNull PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT; /** * Combination of {@link #getOverlayPaths()} and {@link #getSharedLibraryOverlayPaths()} + * * @hide */ + @Immutable.Ignore @Nullable OverlayPaths getAllOverlayPaths(); /** * Credential encrypted /data partition inode. + * + * @hide */ long getCeDataInode(); /** * Fully qualified class names of components explicitly disabled. + * + * @hide */ @NonNull ArraySet<String> getDisabledComponents(); + /** + * @hide + */ @PackageManager.DistractionRestriction int getDistractionFlags(); /** * Fully qualified class names of components explicitly enabled. + * + * @hide */ @NonNull ArraySet<String> getEnabledComponents(); /** * Retrieve the effective enabled state of the package itself. + * + * @hide */ @PackageManager.EnabledState int getEnabledState(); /** + * @hide * @see PackageManager#setHarmfulAppWarning(String, CharSequence) */ @Nullable String getHarmfulAppWarning(); + /** + * @hide + */ @PackageManager.InstallReason int getInstallReason(); /** * Tracks the last calling package to set a specific enabled state for the package. + * + * @hide */ @Nullable String getLastDisableAppCaller(); - /** @hide */ + /** + * @hide + */ + @Immutable.Ignore @Nullable OverlayPaths getOverlayPaths(); - /** @hide */ + /** + * @hide + */ + @Immutable.Ignore @NonNull Map<String, OverlayPaths> getSharedLibraryOverlayPaths(); + /** + * @hide + */ @PackageManager.UninstallReason int getUninstallReason(); /** * @return whether the given fully qualified class name is explicitly enabled + * @hide */ boolean isComponentEnabled(@NonNull String componentName); /** * @return {@link #isComponentEnabled(String)} but for explicitly disabled + * @hide */ boolean isComponentDisabled(@NonNull String componentName); /** + * @hide * @see PackageManager#setApplicationHiddenSettingAsUser(String, boolean, UserHandle) */ boolean isHidden(); /** * @return whether the package is marked as installed for all users + * @hide */ boolean isInstalled(); /** * @return whether the package is marked as an ephemeral app, which restricts permissions, * features, visibility + * @hide */ boolean isInstantApp(); /** * @return whether the package has not been launched since being explicitly stopped + * @hide */ boolean isNotLaunched(); /** * @return whether the package has been stopped, which can occur if it's force-stopped, data * cleared, or just been installed + * @hide */ boolean isStopped(); /** * @return whether the package has been suspended, maybe by the device admin, disallowing its * launch + * @hide */ boolean isSuspended(); /** * @return whether the package was installed as a virtual preload, which may be done as part * of device infrastructure auto installation outside of the initial device image + * @hide */ boolean isVirtualPreload(); /** * The "package:type/entry" form of the theme resource ID previously set as the splash screen. + * + * @hide * @see android.window.SplashScreen#setSplashScreenTheme(int) * @see android.content.res.Resources#getResourceName(int) */ @@ -163,8 +203,10 @@ public interface PackageUserState { * In epoch milliseconds. The timestamp of the first install of the app of the particular user * on the device, surviving past app updates. Different users might have a different first * install time. - * + * <p/> * This does not survive full removal of the app (i.e., uninstalls for all users). + * + * @hide */ long getFirstInstallTime(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index daa4545b4a4a..2d2e062961d2 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -30,6 +30,7 @@ import com.android.server.utils.WatchedArraySet; import java.util.Collections; import java.util.Map; +/** @hide */ class PackageUserStateDefault implements PackageUserStateInternal { @Override diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index da22b1796ac3..a536f90fea6b 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -41,6 +41,7 @@ import java.util.Collections; import java.util.Map; import java.util.Objects; +/** @hide */ @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true) @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock", "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"}) diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java index 96225c012999..46cc830130ef 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java @@ -29,6 +29,8 @@ import com.android.server.utils.WatchedArraySet; * Internal variant of {@link PackageUserState} that includes data not exposed as API. This is * still read-only and should be used inside system server code when possible over the * implementation. + * + * @hide */ public interface PackageUserStateInternal extends PackageUserState, FrameworkPackageUserState { diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 55c305ce812f..063f577bce5f 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -27,6 +27,7 @@ import com.android.server.pm.pkg.component.ParsedProcess; import java.util.List; +/** @hide */ public interface SharedUserApi { @NonNull diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 14f787e4fbea..a8d48aeaa577 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -238,7 +238,6 @@ public class ParsingPackageUtils { * of required system property within the overlay tag. */ public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; - public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8; public static final int PARSE_APK_IN_APEX = 1 << 9; public static final int PARSE_CHATTY = 1 << 31; @@ -257,7 +256,6 @@ public class ParsingPackageUtils { PARSE_IS_SYSTEM_DIR, PARSE_MUST_BE_APK, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, - PARSE_FRAMEWORK_RES_SPLITS }) @Retention(RetentionPolicy.SOURCE) public @interface ParseFlags {} @@ -307,7 +305,7 @@ public class ParsingPackageUtils { isCoreApp); } }); - var parseResult = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null); + var parseResult = parser.parsePackage(input, file, parseFlags); if (parseResult.isError()) { return input.error(parseResult); } @@ -356,14 +354,9 @@ public class ParsingPackageUtils { * not check whether {@code packageFile} has changed since the last parse, it's up to callers to * do so. */ - public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags, - List<File> frameworkSplits) { - if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) - && frameworkSplits.size() > 0 - && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) { - return parseClusterPackage(input, packageFile, frameworkSplits, flags); - } else if (packageFile.isDirectory()) { - return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags); + public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) { + if (packageFile.isDirectory()) { + return parseClusterPackage(input, packageFile, flags); } else { return parseMonolithicPackage(input, packageFile, flags); } @@ -375,28 +368,17 @@ public class ParsingPackageUtils { * identical package name and version codes, a single base APK, and unique * split names. * <p> - * Can also be passed the framework-res.apk file and a list of split apks coming from apexes - * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages - * even if they are in different folders. Note that this code path may have other behaviour - * differences. - * <p> * Note that this <em>does not</em> perform signature verification; that must be done separately * in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}. */ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, - List<File> frameworkSplits, int flags) { - // parseClusterPackageLite should receive no flags (0) for regular splits but we want to - // pass the flags for framework splits + int flags) { int liteParseFlags = 0; - if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) { - liteParseFlags = flags; - } if ((flags & PARSE_APK_IN_APEX) != 0) { liteParseFlags |= PARSE_APK_IN_APEX; } final ParseResult<PackageLite> liteResult = - ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits, - liteParseFlags); + ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags); if (liteResult.isError()) { return input.error(liteResult); } @@ -647,7 +629,7 @@ public class ParsingPackageUtils { final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest); try { final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/, - "coreApp",false); + "coreApp", false); final ParsingPackage pkg = mCallback.startParsingPackage( pkgName, apkPath, codePath, manifestArray, isCoreApp); final ParseResult<ParsingPackage> result = diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 675819a77243..02d70741c0a1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1662,7 +1662,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void toggleNotificationPanel() { IStatusBarService statusBarService = getStatusBarService(); - if (statusBarService != null) { + if (isUserSetupComplete() && statusBarService != null) { try { statusBarService.togglePanel(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index dad9584c6722..9336be5555e4 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -20,10 +20,12 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManagerInternal; import android.media.AudioManager; @@ -32,6 +34,7 @@ import android.media.RingtoneManager; import android.metrics.LogMaker; import android.net.Uri; import android.os.BatteryStats; +import android.os.Bundle; import android.os.Handler; import android.os.IWakeLockCallback; import android.os.Looper; @@ -137,7 +140,9 @@ public class Notifier { private final NotifierHandler mHandler; private final Executor mBackgroundExecutor; private final Intent mScreenOnIntent; + private final Bundle mScreenOnOptions; private final Intent mScreenOffIntent; + private final Bundle mScreenOffOptions; // True if the device should suspend when the screen is off due to proximity. private final boolean mSuspendWhenScreenOffDueToProximityConfig; @@ -199,10 +204,14 @@ public class Notifier { mScreenOnIntent.addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); + mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle(); mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); mScreenOffIntent.addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); + mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter( + new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle(); mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean( com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity); @@ -788,7 +797,8 @@ public class Notifier { if (mActivityManagerInternal.isSystemReady()) { mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null, - mWakeUpBroadcastDone, mHandler, 0, null, null); + AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler, + 0, null, null); } else { EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1); sendNextBroadcast(); @@ -811,7 +821,8 @@ public class Notifier { if (mActivityManagerInternal.isSystemReady()) { mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null, - mGoToSleepBroadcastDone, mHandler, 0, null, null); + AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler, + 0, null, null); } else { EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1); sendNextBroadcast(); diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java index cc275466a5ff..eec3a0259a3e 100644 --- a/services/core/java/com/android/server/resources/ResourcesManagerService.java +++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.IResourcesManager; +import android.content.res.ResourceTimer; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -55,7 +56,7 @@ public class ResourcesManagerService extends SystemService { @Override public void onStart() { - // Intentionally left empty. + ResourceTimer.start(); } private final IBinder mService = new IResourcesManager.Stub() { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index dc4bdaaa8158..ce1157e1d80c 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -18,6 +18,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.media.permission.Identity; import android.media.permission.IdentityContext; import android.media.soundtrigger.ModelParameterRange; @@ -33,6 +34,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import com.android.internal.util.LatencyTracker; + import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; @@ -65,9 +68,12 @@ import java.util.Objects; public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable { private static final String TAG = "SoundTriggerMiddlewareLogging"; private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; + private final @NonNull Context mContext; - public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) { + public SoundTriggerMiddlewareLogging(@NonNull Context context, + @NonNull ISoundTriggerMiddlewareInternal delegate) { mDelegate = delegate; + mContext = context; } @Override @@ -298,6 +304,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt int captureSession) throws RemoteException { try { + startKeyphraseEventLatencyTracking(event); mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession); logVoidReturn("onPhraseRecognition", modelHandle, event); } catch (Exception e) { @@ -347,6 +354,26 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args); } + /** + * Starts the latency tracking log for keyphrase hotword invocation. + * The measurement covers from when the SoundTrigger HAL emits an event to when the + * {@link android.service.voice.VoiceInteractionSession} system UI view is shown. + */ + private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) { + String latencyTrackerTag = null; + if (event.phraseExtras.length > 0) { + latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id; + } + LatencyTracker latencyTracker = LatencyTracker.getInstance(mContext); + // To avoid adding cancel to all of the different failure modes between here and + // showing the system UI, we defensively cancel once. + // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel + // here if any error occurs. + latencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION); + latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION, + latencyTrackerTag); + } + @Override public IBinder asBinder() { return mCallbackDelegate.asBinder(); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 1995e5497e55..807ed14e85ce 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -20,14 +20,14 @@ import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY; import android.annotation.NonNull; import android.content.Context; -import android.media.soundtrigger.ModelParameterRange; -import android.media.soundtrigger.PhraseSoundModel; -import android.media.soundtrigger.RecognitionConfig; -import android.media.soundtrigger.SoundModel; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.Identity; import android.media.permission.PermissionUtil; import android.media.permission.SafeCloseable; +import android.media.soundtrigger.ModelParameterRange; +import android.media.soundtrigger.PhraseSoundModel; +import android.media.soundtrigger.RecognitionConfig; +import android.media.soundtrigger.SoundModel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -226,12 +226,13 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic HalFactory[] factories = new HalFactory[]{new DefaultHalFactory()}; publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, - new SoundTriggerMiddlewareService(new SoundTriggerMiddlewareLogging( - new SoundTriggerMiddlewarePermission( - new SoundTriggerMiddlewareValidation( - new SoundTriggerMiddlewareImpl(factories, - new AudioSessionProviderImpl())), - getContext())), getContext())); + new SoundTriggerMiddlewareService( + new SoundTriggerMiddlewareLogging(getContext(), + new SoundTriggerMiddlewarePermission( + new SoundTriggerMiddlewareValidation( + new SoundTriggerMiddlewareImpl(factories, + new AudioSessionProviderImpl())), + getContext())), getContext())); } } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index e21feae65c97..886e8e687e5a 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -93,6 +93,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; @@ -254,6 +255,7 @@ public class TrustManagerService extends SystemService { return; } if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + checkNewAgents(); mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker); @@ -262,7 +264,7 @@ public class TrustManagerService extends SystemService { refreshAgentList(UserHandle.USER_ALL); refreshDeviceLockedForUser(UserHandle.USER_ALL); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { - maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_SYSTEM); + maybeEnableFactoryTrustAgents(UserHandle.USER_SYSTEM); } } @@ -685,7 +687,7 @@ public class TrustManagerService extends SystemService { */ public void lockUser(int userId) { mLockPatternUtils.requireStrongAuth( - StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId); + StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId); try { WindowManagerGlobal.getWindowManagerService().lockNow(null); } catch (RemoteException e) { @@ -1083,7 +1085,7 @@ public class TrustManagerService extends SystemService { return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } - private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) { + private void maybeEnableFactoryTrustAgents(int userId) { if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) { return; @@ -1100,8 +1102,7 @@ public class TrustManagerService extends SystemService { } else { // A default agent is not set; perform regular trust agent discovery for (ResolveInfo resolveInfo : resolveInfos) { ComponentName componentName = getComponentName(resolveInfo); - int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags; - if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { + if (!isSystemTrustAgent(resolveInfo)) { Log.i(TAG, "Leaving agent " + componentName + " disabled because package " + "is not a system package."); continue; @@ -1110,13 +1111,88 @@ public class TrustManagerService extends SystemService { } } - List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId); - discoveredAgents.addAll(previouslyEnabledAgents); - utils.setEnabledTrustAgents(discoveredAgents, userId); + enableNewAgents(discoveredAgents, userId); Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId); } + private void checkNewAgents() { + for (UserInfo userInfo : mUserManager.getAliveUsers()) { + checkNewAgentsForUser(userInfo.id); + } + } + + /** + * Checks for any new trust agents that become available after the first boot, add them to the + * list of known agents, and enable them if they should be enabled by default. + */ + private void checkNewAgentsForUser(int userId) { + // When KNOWN_TRUST_AGENTS_INITIALIZED is not set, only update the known agent list but do + // not enable any agent. + // These agents will be enabled by #maybeEnableFactoryTrustAgents if this is the first time + // that this device boots and TRUST_AGENTS_INITIALIZED is not already set. + // Otherwise, these agents may have been manually disabled by the user, and we should not + // re-enable them. + if (0 == Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 0, userId)) { + initializeKnownAgents(userId); + return; + } + + List<ComponentName> knownAgents = mLockPatternUtils.getKnownTrustAgents(userId); + List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(), + userId); + ArraySet<ComponentName> newAgents = new ArraySet<>(agentInfoList.size()); + ArraySet<ComponentName> newSystemAgents = new ArraySet<>(agentInfoList.size()); + + for (ResolveInfo agentInfo : agentInfoList) { + ComponentName agentComponentName = getComponentName(agentInfo); + if (knownAgents.contains(agentComponentName)) { + continue; + } + newAgents.add(agentComponentName); + if (isSystemTrustAgent(agentInfo)) { + newSystemAgents.add(agentComponentName); + } + } + + if (newAgents.isEmpty()) { + return; + } + + ArraySet<ComponentName> updatedKnowAgents = new ArraySet<>(knownAgents); + updatedKnowAgents.addAll(newAgents); + mLockPatternUtils.setKnownTrustAgents(updatedKnowAgents, userId); + + // Do not auto enable new trust agents when the default agent is set + boolean hasDefaultAgent = getDefaultFactoryTrustAgent(mContext) != null; + if (!hasDefaultAgent) { + enableNewAgents(newSystemAgents, userId); + } + } + + private void enableNewAgents(Collection<ComponentName> agents, int userId) { + if (agents.isEmpty()) { + return; + } + + ArraySet<ComponentName> agentsToEnable = new ArraySet<>(agents); + agentsToEnable.addAll(mLockPatternUtils.getEnabledTrustAgents(userId)); + mLockPatternUtils.setEnabledTrustAgents(agentsToEnable, userId); + } + + private void initializeKnownAgents(int userId) { + List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(), + userId); + ArraySet<ComponentName> agentComponentNames = new ArraySet<>(agentInfoList.size()); + for (ResolveInfo agentInfo : agentInfoList) { + agentComponentNames.add(getComponentName(agentInfo)); + } + mLockPatternUtils.setKnownTrustAgents(agentComponentNames, userId); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, userId); + } + /** * Returns the {@link ComponentName} for the default trust agent, or {@code null} if there * is no trust agent set. @@ -1152,6 +1228,10 @@ public class TrustManagerService extends SystemService { return allowedAgents; } + private static boolean isSystemTrustAgent(ResolveInfo agentInfo) { + return (agentInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + // Agent dispatch and aggregation private boolean aggregateIsTrusted(int userId) { @@ -1825,7 +1905,13 @@ public class TrustManagerService extends SystemService { } @Override + public void onPackageAdded(String packageName, int uid) { + checkNewAgentsForUser(UserHandle.getUserId(uid)); + } + + @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { + checkNewAgentsForUser(UserHandle.getUserId(uid)); // We're interested in all changes, even if just some components get enabled / disabled. return true; } @@ -1860,7 +1946,7 @@ public class TrustManagerService extends SystemService { action)) { int userId = getUserId(intent); if (userId > 0) { - maybeEnableFactoryTrustAgents(mLockPatternUtils, userId); + maybeEnableFactoryTrustAgents(userId); } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { int userId = getUserId(intent); @@ -1997,7 +2083,7 @@ public class TrustManagerService extends SystemService { if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) { if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout"); mLockPatternUtils.requireStrongAuth( - mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId); + mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId); } maybeLockScreen(mUserId); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 804689a219b4..f1b011f17070 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -965,13 +965,6 @@ final class AccessibilityController { availableBounds.op(navBarInsets, Region.Op.DIFFERENCE); } - // Count letterbox into nonMagnifiedBounds - if (windowState.areAppWindowBoundsLetterboxed()) { - Region letterboxBounds = getLetterboxBounds(windowState); - nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION); - availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE); - } - // Update accounted bounds Region accountedBounds = mTempRegion2; accountedBounds.set(mMagnificationRegion); @@ -1411,20 +1404,6 @@ final class AccessibilityController { return source != null ? source.getFrame() : EMPTY_RECT; } - static Region getLetterboxBounds(WindowState windowState) { - final ActivityRecord appToken = windowState.mActivityRecord; - if (appToken == null) { - return new Region(); - } - final Rect letterboxInsets = appToken.getLetterboxInsets(); - final Rect nonLetterboxRect = windowState.getBounds(); - nonLetterboxRect.inset(letterboxInsets); - final Region letterboxBounds = new Region(); - letterboxBounds.set(windowState.getBounds()); - letterboxBounds.op(nonLetterboxRect, Region.Op.DIFFERENCE); - return letterboxBounds; - } - /** * This class encapsulates the functionality related to computing the windows * reported for accessibility purposes. These windows are all windows a sighted @@ -1678,18 +1657,17 @@ final class AccessibilityController { a11yWindow.getTouchableRegionInScreen(touchableRegion); unaccountedSpace.op(touchableRegion, unaccountedSpace, Region.Op.REVERSE_DIFFERENCE); - // Account for the space of letterbox. - final Region letterboxBounds = mTempRegion1; - if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) { - unaccountedSpace.op(letterboxBounds, - unaccountedSpace, Region.Op.REVERSE_DIFFERENCE); - } } } private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow, Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) { final WindowInfo window = a11yWindow.getWindowInfo(); + if (window.token == null) { + // The window was used in calculating visible windows but does not have an + // associated IWindow token, so exclude it from the list returned to accessibility. + return; + } window.regionInScreen.set(regionInScreen); window.layer = tokenOut.size(); out.add(window); diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index 79ae66250a63..d0c381e9debc 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -153,7 +153,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { for (InputWindowHandle window : windowHandles) { final boolean visible = (window.inputConfig & InputConfig.NOT_VISIBLE) == 0; final boolean isNotClone = (window.inputConfig & InputConfig.CLONE) == 0; - if (visible && window.getWindow() != null && isNotClone) { + final boolean hasTouchableRegion = !window.touchableRegion.isEmpty(); + final boolean hasNonEmptyFrame = + (window.frameBottom != window.frameTop) && (window.frameLeft + != window.frameRight); + if (visible && isNotClone && hasTouchableRegion && hasNonEmptyFrame) { tempVisibleWindows.add(window); } } @@ -321,10 +325,20 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { // the old and new windows at the same index should be the // same, otherwise something changed. for (int i = 0; i < windowsCount; i++) { - final InputWindowHandle newWindow = newWindows.get(i); - final InputWindowHandle oldWindow = oldWindows.get(i); + final IWindow newWindowToken = newWindows.get(i).getWindow(); + final IWindow oldWindowToken = oldWindows.get(i).getWindow(); + final boolean hasNewWindowToken = newWindowToken != null; + final boolean hasOldWindowToken = oldWindowToken != null; - if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) { + // If window token presence has changed then the windows have changed. + if (hasNewWindowToken != hasOldWindowToken) { + return true; + } + + // If both old and new windows had window tokens, but those tokens differ, + // then the windows have changed. + if (hasNewWindowToken && hasOldWindowToken + && !newWindowToken.asBinder().equals(oldWindowToken.asBinder())) { return true; } } @@ -374,7 +388,8 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { for (int index = inputWindowHandles.size() - 1; index >= 0; index--) { final Matrix windowTransformMatrix = mTempMatrix2; final InputWindowHandle windowHandle = inputWindowHandles.get(index); - final IBinder iBinder = windowHandle.getWindow().asBinder(); + final IBinder iBinder = + windowHandle.getWindow() != null ? windowHandle.getWindow().asBinder() : null; if (getWindowTransformMatrix(iBinder, windowTransformMatrix)) { generateMagnificationSpecInverseMatrix(windowHandle, currentMagnificationSpec, @@ -609,7 +624,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { private boolean mIgnoreDuetoRecentsAnimation; private final Region mTouchableRegionInScreen = new Region(); private final Region mTouchableRegionInWindow = new Region(); - private final Region mLetterBoxBounds = new Region(); private WindowInfo mWindowInfo; @@ -628,11 +642,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { final AccessibilityWindow instance = new AccessibilityWindow(); - instance.mWindow = inputWindowHandle.getWindow(); + instance.mWindow = window; instance.mDisplayId = inputWindowHandle.displayId; instance.mInputConfig = inputWindowHandle.inputConfig; instance.mType = inputWindowHandle.layoutParamsType; - instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder); + instance.mIsPIPMenu = window != null && window.asBinder().equals(pipIBinder); // TODO (b/199357848): gets the private flag of the window from other way. instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0; @@ -644,11 +658,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null && controller.shouldIgnoreForAccessibility(windowState); - // TODO (b/199358388) : gets the letterbox bounds of the window from other way. - if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) { - getLetterBoxBounds(windowState, instance.mLetterBoxBounds); - } - final Rect windowFrame = new Rect(inputWindowHandle.frameLeft, inputWindowHandle.frameTop, inputWindowHandle.frameRight, inputWindowHandle.frameBottom); @@ -724,21 +733,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { } /** - * Gets the letter box bounds if activity bounds are letterboxed - * or letterboxed for display cutout. - * - * @return {@code true} there's a letter box bounds. - */ - public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) { - if (mLetterBoxBounds.isEmpty()) { - return false; - } - - outBounds.set(mLetterBoxBounds); - return true; - } - - /** * @return true if this window should be magnified. */ public boolean shouldMagnify() { @@ -841,7 +835,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { WindowInfo windowInfo = WindowInfo.obtain(); windowInfo.displayId = window.mDisplayId; windowInfo.type = window.mType; - windowInfo.token = window.mWindow.asBinder(); + windowInfo.token = window.mWindow != null ? window.mWindow.asBinder() : null; windowInfo.hasFlagWatchOutsideTouch = (window.mInputConfig & InputConfig.WATCH_OUTSIDE_TOUCH) != 0; // Set it to true to be consistent with the legacy implementation. @@ -849,18 +843,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { return windowInfo; } - private static void getLetterBoxBounds(WindowState windowState, Region outRegion) { - final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets(); - final Rect nonLetterboxRect = windowState.getBounds(); - - nonLetterboxRect.inset(letterboxInsets); - outRegion.set(windowState.getBounds()); - outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE); - } - @Override public String toString() { - String builder = "A11yWindow=[" + mWindow.asBinder() + String windowToken = + mWindow != null ? mWindow.asBinder().toString() : "(no window token)"; + return "A11yWindow=[" + windowToken + ", displayId=" + mDisplayId + ", inputConfig=0x" + Integer.toHexString(mInputConfig) + ", type=" + mType @@ -871,12 +858,9 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { + ", isTrustedOverlay=" + isTrustedOverlay() + ", regionInScreen=" + mTouchableRegionInScreen + ", touchableRegion=" + mTouchableRegionInWindow - + ", letterBoxBounds=" + mLetterBoxBounds + ", isPIPMenu=" + mIsPIPMenu + ", windowInfo=" + mWindowInfo + "]"; - - return builder; } } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 7067ae474aad..08d2e69846d3 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -237,9 +237,21 @@ class ActivityMetricsLogger { if (mAssociatedTransitionInfo == null) { launchResult = ":failed"; } else { - launchResult = (abort ? ":canceled:" : mAssociatedTransitionInfo.mProcessSwitch - ? ":completed:" : ":completed-same-process:") - + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName; + final String status; + if (abort) { + status = ":canceled:"; + } else if (!mAssociatedTransitionInfo.mProcessSwitch) { + status = ":completed-same-process:"; + } else { + if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) { + status = ":completed-hot:"; + } else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) { + status = ":completed-warm:"; + } else { + status = ":completed-cold:"; + } + } + launchResult = status + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName; } // Put a supplement trace as the description of the async trace with the same id. Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName + launchResult); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 98d3adc71fe6..d8d75ed9790f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1586,11 +1586,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (oldParent != null) { oldParent.cleanUpActivityReferences(this); + // Update isVisibleRequested value of parent TaskFragment and send the callback to the + // client side if needed. + oldParent.onActivityVisibleRequestedChanged(); } - if (newParent != null && isState(RESUMED)) { - newParent.setResumedActivity(this, "onParentChanged"); - mImeInsetsFrozenUntilStartInput = false; + if (newParent != null) { + // Update isVisibleRequested value of parent TaskFragment and send the callback to the + // client side if needed. + newParent.onActivityVisibleRequestedChanged(); + if (isState(RESUMED)) { + newParent.setResumedActivity(this, "onParentChanged"); + mImeInsetsFrozenUntilStartInput = false; + } } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -5094,6 +5102,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null) { + taskFragment.onActivityVisibleRequestedChanged(); + } setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); @@ -9550,7 +9562,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean showToCurrentUser() { - return mShowForAllUsers || mWmService.isCurrentProfile(mUserId); + return mShowForAllUsers || mWmService.isUserVisible(mUserId); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index bc1c6b2e8f3a..1d70146d5183 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -892,9 +892,10 @@ class ActivityStarter { final int userId = aInfo != null && aInfo.applicationInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + final int launchMode = aInfo != null ? aInfo.launchMode : 0; if (err == ActivityManager.START_SUCCESS) { Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) - + "} from uid " + callingUid); + + "} with " + launchModeToString(launchMode) + " from uid " + callingUid); } ActivityRecord sourceRecord = null; @@ -1847,7 +1848,7 @@ class ActivityStarter { ActivityRecord targetTopActivity = targetTask != null ? targetTask.getTopNonFinishingActivity() : null; boolean passesAsmChecks = newTask - ? mRootWindowContainer.hasResumedActivity(callerUid) + ? mService.mVisibleActivityProcessTracker.hasResumedActivity(callerUid) : targetTopActivity != null && targetTopActivity.getUid() == callerUid; if (!passesAsmChecks) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index e1f40a8cbdb7..83953022dd72 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; @@ -1445,6 +1446,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean canLaunchDreamActivity(String packageName) { if (!mDreaming || packageName == null) { + ProtoLog.e(WM_DEBUG_DREAM, "Cannot launch dream activity due to invalid state. " + + "dreaming: %b packageName: %s", mDreaming, packageName); return false; } final DreamManagerInternal dreamManager = @@ -1460,6 +1463,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (activeDoze != null && packageName.equals(activeDoze.getPackageName())) { return true; } + ProtoLog.e(WM_DEBUG_DREAM, + "Dream packageName does not match active dream. Package %s does not match %s or %s", + packageName, String.valueOf(activeDream), String.valueOf(activeDoze)); return false; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 28cd0016b019..5bddae66fed3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1183,10 +1183,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } if (!displayContent.isPrivate()) { - // Anyone can launch on a public display. - ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch on public " - + "display"); - return true; + // Checks if the caller can be shown in the given public display. + int userId = UserHandle.getUserId(callingUid); + int displayId = display.getDisplayId(); + boolean allowed = mWindowManager.mUmInternal.isUserVisible(userId, displayId); + ProtoLog.d(WM_DEBUG_TASKS, + "Launch on display check: %s launch for userId=%d on displayId=%d", + (allowed ? "allow" : "disallow"), userId, displayId); + return allowed; } // Check if the caller is the owner of the display. diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 1266db5bff98..8c5f05365837 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -202,10 +202,16 @@ class AsyncRotationController extends FadeAnimationController implements Consume // target windows. But the windows still need to use sync transaction to keep the appearance // in previous rotation, so request a no-op sync to keep the state. for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST + && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) { + // Expect a screenshot layer will cover the non seamless windows. + continue; + } final WindowToken token = mTargetWindowTokens.keyAt(i); for (int j = token.getChildCount() - 1; j >= 0; j--) { // TODO(b/234585256): The consumer should be handleFinishDrawing(). token.getChildAt(j).applyWithNextDraw(t -> {}); + if (DEBUG) Slog.d(TAG, "Sync draw for " + token.getChildAt(j)); } } mIsSyncDrawRequested = true; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 28093074303d..c6fe017b1917 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4164,7 +4164,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } ProtoLog.i(WM_DEBUG_IME, "setInputMethodTarget %s", target); - final boolean layeringTargetChanged = target != mImeLayeringTarget; + boolean shouldUpdateImeParent = target != mImeLayeringTarget; mImeLayeringTarget = target; // 1. Reparent the IME container window to the target root DA to get the correct bounds and @@ -4172,10 +4172,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // is not organized (see FEATURE_IME and updateImeParent). if (target != null && !mImeWindowsContainer.isOrganized()) { RootDisplayArea targetRoot = target.getRootDisplayArea(); - if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) { - // Reposition the IME container to the target root to get the correct bounds and - // config. - targetRoot.placeImeContainer(mImeWindowsContainer); + if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea() + // Try reparent the IME container to the target root to get the bounds and + // config that match the target window. + && targetRoot.placeImeContainer(mImeWindowsContainer)) { + // Update the IME surface parent since the IME container window has been reparented. + shouldUpdateImeParent = true; // Directly hide the IME window so it doesn't flash immediately after reparenting. // InsetsController will make IME visible again before animating it. if (mInputMethodWindow != null) { @@ -4192,7 +4194,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // 4. Update the IME control target to apply any inset change and animation. // 5. Reparent the IME container surface to either the input target app, or the IME window // parent. - updateImeControlTarget(layeringTargetChanged); + updateImeControlTarget(shouldUpdateImeParent); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index dcc16ebd88f5..d54f77a73661 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -19,17 +19,16 @@ package com.android.server.wm; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; -import static com.android.server.wm.WindowContainerProto.IDENTIFIER; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowStateProto.IDENTIFIER; import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; import android.util.ArrayMap; -import android.util.proto.ProtoOutputStream; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import android.view.IWindow; import android.view.InputApplicationHandle; import android.view.InputChannel; diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java index d94bb9e89252..092adc37d497 100644 --- a/services/core/java/com/android/server/wm/RootDisplayArea.java +++ b/services/core/java/com/android/server/wm/RootDisplayArea.java @@ -22,8 +22,10 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; +import android.util.Slog; import com.android.server.policy.WindowManagerPolicy; @@ -76,8 +78,10 @@ class RootDisplayArea extends DisplayArea.Dimmable { /** * Places the IME container below this root, so that it's bounds and config will be updated to * match the root. + * + * @return {@code true} if the IME container is reparented to this root. */ - void placeImeContainer(DisplayArea.Tokens imeContainer) { + boolean placeImeContainer(DisplayArea.Tokens imeContainer) { final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea(); List<Feature> features = mFeatures; @@ -94,11 +98,23 @@ class RootDisplayArea extends DisplayArea.Dimmable { previousRoot.updateImeContainerForLayers(null /* imeContainer */); imeContainer.reparent(imeDisplayAreas.get(0), POSITION_TOP); updateImeContainerForLayers(imeContainer); - return; + return true; } } - throw new IllegalStateException( - "There is no FEATURE_IME_PLACEHOLDER in this root to place the IME container"); + + // Some device UX may not have the need to update the IME bounds and position for IME target + // in a child DisplayArea, so instead of throwing exception, we just allow the IME container + // to remain in its previous root. + if (!isDescendantOf(previousRoot)) { + // When this RootDisplayArea is a descendant of the current RootDisplayArea, it will be + // at the APPLICATION_LAYER, and the IME container will always be on top and have bounds + // equal or larger than the input target. + // If it is not a descendant, the DisplayAreaPolicy owner should make sure the IME is + // working correctly. Print a warning in case they are not. + Slog.w(TAG_WM, "The IME target is not in the same root as the IME container, but there " + + "is no DisplayArea of FEATURE_IME_PLACEHOLDER in the target RootDisplayArea"); + } + return false; } /** diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index f66ba383b44f..ba834def88c7 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1799,10 +1799,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedActivity); } - boolean hasResumedActivity(int uid) { - return forAllActivities(ar -> ar.isState(RESUMED) && ar.getUid() == uid); - } - boolean isTopDisplayFocusedRootTask(Task task) { return task != null && task == getTopDisplayFocusedRootTask(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 837045c0019b..15088704fd9b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -186,6 +186,7 @@ import android.view.WindowManager.TransitionOldType; import android.window.ITaskOrganizer; import android.window.PictureInPictureSurfaceTransaction; import android.window.StartingWindowInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -2679,6 +2680,7 @@ class Task extends TaskFragment { if (isRootTask()) { updateSurfaceBounds(); } + sendTaskFragmentParentInfoChangedIfNeeded(); } boolean isResizeable() { @@ -2914,7 +2916,7 @@ class Task extends TaskFragment { @Override boolean showToCurrentUser() { return mForceShowForAllUsers || showForAllUsers() - || mWmService.isCurrentProfile(getTopMostTask().mUserId); + || mWmService.isUserVisible(getTopMostTask().mUserId); } void setForceShowForAllUsers(boolean forceShowForAllUsers) { @@ -3320,8 +3322,9 @@ class Task extends TaskFragment { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "applyAnimationUnchecked, control: %s, task: %s, transit: %s", control, asTask(), AppTransition.appTransitionOldToString(transit)); + final int size = sources != null ? sources.size() : 0; control.addTaskToTargets(this, (type, anim) -> { - for (int i = 0; i < sources.size(); ++i) { + for (int i = 0; i < size; ++i) { sources.get(i).onAnimationFinished(type, anim); } }); @@ -3513,6 +3516,33 @@ class Task extends TaskFragment { return info; } + /** + * Returns the {@link TaskFragmentParentInfo} which will send to the client + * {@link android.window.TaskFragmentOrganizer} + */ + TaskFragmentParentInfo getTaskFragmentParentInfo() { + return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested()); + } + + @Override + void onActivityVisibleRequestedChanged() { + if (mVisibleRequested != isVisibleRequested()) { + sendTaskFragmentParentInfoChangedIfNeeded(); + } + } + + void sendTaskFragmentParentInfoChangedIfNeeded() { + if (!isLeafTask()) { + // Only send parent info changed event for leaf task. + return; + } + final TaskFragment childOrganizedTf = + getTaskFragment(TaskFragment::isOrganizedTaskFragment); + if (childOrganizedTf != null) { + childOrganizedTf.sendTaskFragmentParentInfoChanged(); + } + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index de4c84c27a69..2cfc563b0d6e 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -298,6 +298,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { final Point mLastSurfaceSize = new Point(); + /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */ + boolean mVisibleRequested; + private final Rect mTmpBounds = new Rect(); private final Rect mTmpFullBounds = new Rect(); /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */ @@ -2382,6 +2385,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } + void sendTaskFragmentParentInfoChanged() { + final Task parentTask = getParent().asTask(); + if (mTaskFragmentOrganizer != null && parentTask != null) { + mTaskFragmentOrganizerController + .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask); + } + } + private void sendTaskFragmentAppeared() { if (mTaskFragmentOrganizer != null) { mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this); @@ -2417,7 +2428,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mRemoteToken.toWindowContainerToken(), getConfiguration(), getNonFinishingActivityCount(), - isVisible(), + isVisibleRequested(), childActivities, positionInParent, mClearedTaskForReuse, @@ -2669,6 +2680,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); } + void onActivityVisibleRequestedChanged() { + final boolean isVisibleRequested = isVisibleRequested(); + if (mVisibleRequested == isVisibleRequested) { + return; + } + mVisibleRequested = isVisibleRequested; + final TaskFragment parentTf = getParent().asTaskFragment(); + if (parentTf != null) { + parentTf.onActivityVisibleRequestedChanged(); + } + } + String toFullString() { final StringBuilder sb = new StringBuilder(128); sb.append(this); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 8c037a7390b1..2d5c9897a82c 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -49,6 +49,7 @@ import android.view.WindowManager; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -118,10 +119,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>(); /** - * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the + * Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the * organizer. */ - private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs = + private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos = new SparseArray<>(); /** @@ -225,7 +226,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr taskId = mTaskFragmentTaskIds.remove(tf); if (!mTaskFragmentTaskIds.containsValue(taskId)) { // No more TaskFragment in the Task. - mLastSentTaskFragmentParentConfigs.remove(taskId); + mLastSentTaskFragmentParentInfos.remove(taskId); } } else { // This can happen if the appeared wasn't sent before remove. @@ -260,25 +261,27 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Nullable - TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged( - @NonNull Task task) { + TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) { final int taskId = task.mTaskId; // Check if the parent info is different from the last reported parent info. - final Configuration taskConfig = task.getConfiguration(); - final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId); - if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig) - && taskConfig.windowConfiguration.getWindowingMode() - == lastParentConfig.windowConfiguration.getWindowingMode()) { + final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo(); + final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos + .get(taskId); + final Configuration lastParentConfig = lastParentInfo != null + ? lastParentInfo.getConfiguration() : null; + if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo) + && configurationsAreEqualForOrganizer(parentInfo.getConfiguration(), + lastParentConfig)) { return null; } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment parent info changed name=%s parentTaskId=%d", task.getName(), taskId); - mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig)); + mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo)); return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(taskId) - .setTaskConfiguration(taskConfig); + .setTaskFragmentParentInfo(parentInfo); } @NonNull @@ -646,6 +649,34 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .build()); } + void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, + @NonNull Task task) { + validateAndGetState(organizer); + final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent( + organizer, task); + if (pendingEvent == null) { + addPendingEvent(new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + .setTask(task) + .build()); + } + } + + @Nullable + private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent( + @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) { + final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents + .get(organizer.asBinder()); + for (int i = events.size() - 1; i >= 0; i--) { + final PendingTaskFragmentEvent event = events.get(i); + if (task == event.mTask + && event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) { + return event; + } + } + return null; + } + private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) { mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event); } @@ -848,7 +879,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) { - if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) { + if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR + // Always send parent info changed to update task visibility + || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) { return true; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 723aa19bc2b5..93d9b0e724ce 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -42,6 +42,7 @@ import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; @@ -1860,14 +1861,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Whether this is in a Task with embedded activity. flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; } - final Rect taskBounds = parentTask.getBounds(); - final Rect startBounds = mAbsoluteBounds; - final Rect endBounds = wc.getBounds(); - if (taskBounds.width() == startBounds.width() - && taskBounds.height() == startBounds.height() - && taskBounds.width() == endBounds.width() - && taskBounds.height() == endBounds.height()) { - // Whether the container fills the Task bounds before and after the transition. + if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) { + // The starting window should cover all windows inside the leaf Task. + flags |= FLAG_IS_BEHIND_STARTING_WINDOW; + } + if (isWindowFillingTask(wc, parentTask)) { + // Whether the container fills its parent Task bounds. flags |= FLAG_FILLS_TASK; } } @@ -1889,6 +1888,22 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } return flags; } + + /** Whether the container fills its parent Task bounds before and after the transition. */ + private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) { + final Rect taskBounds = parentTask.getBounds(); + final int taskWidth = taskBounds.width(); + final int taskHeight = taskBounds.height(); + final Rect startBounds = mAbsoluteBounds; + final Rect endBounds = wc.getBounds(); + // Treat it as filling the task if it is not visible. + final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible + || (taskWidth == startBounds.width() && taskHeight == startBounds.height()); + final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested() + || (taskWidth == endBounds.width() && taskHeight == endBounds.height()); + return isInvisibleOrFillingTaskBeforeTransition + && isInVisibleOrFillingTaskAfterTransition; + } } /** diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8cc0d5dd8c1b..69d86b60302b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2195,6 +2195,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound); } + @Nullable + TaskFragment getTaskFragment(Predicate<TaskFragment> callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final TaskFragment tf = mChildren.get(i).getTaskFragment(callback); + if (tf != null) { + return tf; + } + } + return null; + } + WindowState getWindow(Predicate<WindowState> callback) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState w = mChildren.get(i).getWindow(callback); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 49e7a0c8c745..e23e2065e067 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -123,7 +123,10 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_ import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; @@ -155,6 +158,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityThread; @@ -315,6 +319,7 @@ import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.input.InputManagerService; +import com.android.server.pm.UserManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; @@ -504,15 +509,9 @@ public class WindowManagerService extends IWindowManager.Stub }; /** - * Current user when multi-user is enabled. Don't show windows of - * non-current user. Also see mCurrentProfileIds. + * Current user when multi-user is enabled. Don't show windows of non-current user. */ - int mCurrentUserId; - /** - * Users that are profiles of the current user. These are also allowed to show windows - * on the current user. - */ - int[] mCurrentProfileIds = new int[] {}; + @UserIdInt int mCurrentUserId; final Context mContext; @@ -534,6 +533,7 @@ public class WindowManagerService extends IWindowManager.Stub final IActivityManager mActivityManager; final ActivityManagerInternal mAmInternal; + final UserManagerInternal mUmInternal; final AppOpsManager mAppOps; final PackageManagerInternal mPmInternal; @@ -1263,6 +1263,7 @@ public class WindowManagerService extends IWindowManager.Stub mActivityManager = ActivityManager.getService(); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); + mUmInternal = LocalServices.getService(UserManagerInternal.class); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); AppOpsManager.OnOpChangedInternalListener opListener = new AppOpsManager.OnOpChangedInternalListener() { @@ -2604,10 +2605,22 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) { String reason = null; if (winAnimator.applyAnimationLocked(transit, false)) { + // This is a WMCore-driven window animation. reason = "applyAnimation"; focusMayChange = true; win.mAnimatingExit = true; - } else if (win.isExitAnimationRunningSelfOrParent()) { + } else if ( + // This is already animating via a WMCore-driven window animation + win.isSelfAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION) + // Or already animating as part of a legacy app-transition + || win.isAnimating(PARENTS | TRANSITION, + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + // Or already animating as part of a shell-transition. + || (win.inTransition() + // Filter out non-app windows since transitions don't animate those + // (but may still "wait" on them for readiness) + && (win.mActivityRecord != null || win.mIsWallpaper))) { + // TODO(b/247005789): set mAnimatingExit somewhere in shell-transitions setup. reason = "animating"; win.mAnimatingExit = true; } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win) @@ -3558,16 +3571,9 @@ public class WindowManagerService extends IWindowManager.Stub confirm); } - public void setCurrentProfileIds(final int[] currentProfileIds) { - synchronized (mGlobalLock) { - mCurrentProfileIds = currentProfileIds; - } - } - - public void setCurrentUser(final int newUserId, final int[] currentProfileIds) { + public void setCurrentUser(@UserIdInt int newUserId) { synchronized (mGlobalLock) { mCurrentUserId = newUserId; - mCurrentProfileIds = currentProfileIds; mPolicy.setCurrentUserLw(newUserId); mKeyguardDisableHandler.setCurrentUser(newUserId); @@ -3590,12 +3596,8 @@ public class WindowManagerService extends IWindowManager.Stub } /* Called by WindowState */ - boolean isCurrentProfile(int userId) { - if (userId == mCurrentUserId) return true; - for (int i = 0; i < mCurrentProfileIds.length; i++) { - if (mCurrentProfileIds[i] == userId) return true; - } - return false; + boolean isUserVisible(@UserIdInt int userId) { + return mUmInternal.isUserVisible(userId); } public void enableScreenAfterBoot() { @@ -8497,7 +8499,10 @@ public class WindowManagerService extends IWindowManager.Stub if (mFocusedInputTarget != t && mFocusedInputTarget != null) { mFocusedInputTarget.handleTapOutsideFocusOutsideSelf(); } + // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities. + mAtmService.mTaskSupervisor.mUserLeaving = true; t.handleTapOutsideFocusInsideSelf(); + mAtmService.mTaskSupervisor.mUserLeaving = false; } @VisibleForTesting @@ -9277,46 +9282,4 @@ public class WindowManagerService extends IWindowManager.Stub "Unexpected letterbox background type: " + letterboxBackgroundType); } } - - @Override - public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs, - ScreenCapture.ScreenCaptureListener listener) { - Slog.d(TAG, "captureDisplay"); - if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) { - throw new SecurityException("Requires READ_FRAME_BUFFER permission"); - } - - ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener); - } - - @VisibleForTesting - ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId, - @Nullable ScreenCapture.CaptureArgs captureArgs) { - final SurfaceControl displaySurfaceControl; - synchronized (mGlobalLock) { - DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent == null) { - throw new IllegalArgumentException("Trying to screenshot and invalid display: " - + displayId); - } - - displaySurfaceControl = displayContent.getSurfaceControl(); - - if (captureArgs == null) { - captureArgs = new ScreenCapture.CaptureArgs.Builder<>() - .build(); - } - - if (captureArgs.mSourceCrop.isEmpty()) { - displayContent.getBounds(mTmpRect); - mTmpRect.offsetTo(0, 0); - } else { - mTmpRect.set(captureArgs.mSourceCrop); - } - } - - return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs) - .setSourceCrop(mTmpRect) - .build(); - } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 9456f0fcced7..2e1477ddf0f1 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -25,6 +25,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER; @@ -1007,6 +1008,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub isInLockTaskMode); break; } + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: { + final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + if (activity == null || activity.finishing) { + break; + } + if (activity.isVisible()) { + // Prevent the transition from being executed too early if the activity is + // visible. + activity.finishIfPossible("finish-activity-op", false /* oomAdj */); + } else { + activity.destroyIfPossible("finish-activity-op"); + } + break; + } case HIERARCHY_OP_TYPE_LAUNCH_TASK: { mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "launchTask HierarchyOp"); @@ -1620,6 +1635,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub organizer); } break; + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: + // Allow finish activity if it has the activity token. + break; default: // Other types of hierarchy changes are not allowed. String msg = "Permission Denial: " + func + " from pid=" diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index df7b8bb4ec9b..4004d65e81af 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3467,7 +3467,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP "Setting visibility of " + this + ": " + clientVisible); mClient.dispatchAppVisibility(clientVisible); } catch (RemoteException e) { + // The remote client fails to process the visibility message. That means it is in a + // wrong state. E.g. the binder buffer is running out or the binder threads are dead. + // The window visibility is out-of-sync that may cause blank content or left over, so + // just kill it. And if it is a window of foreground activity, the activity can be + // restarted automatically if needed. Slog.w(TAG, "Exception thrown during dispatchAppVisibility " + this, e); + android.os.Process.killProcess(mSession.mPid); } } @@ -3684,7 +3690,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } return win.showForAllUsers() - || mWmService.isCurrentProfile(win.mShowUserId); + || mWmService.isUserVisible(win.mShowUserId); } private static void applyInsets(Region outRegion, Rect frame, Rect inset) { diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp index 61f2b1411a0b..02e5061a3ac6 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -17,6 +17,7 @@ #include <android_util_Binder.h> #include <gui/SurfaceComposerClient.h> #include <jni.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> namespace android { @@ -33,6 +34,27 @@ static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { SurfaceComposerClient::destroyDisplay(token); } +static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject, + jintArray jHdrTypes) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObject)); + if (token == nullptr || jHdrTypes == nullptr) return; + + ScopedIntArrayRO hdrTypes(env, jHdrTypes); + size_t numHdrTypes = hdrTypes.size(); + + std::vector<ui::Hdr> hdrTypesVector; + hdrTypesVector.reserve(numHdrTypes); + for (int i = 0; i < numHdrTypes; i++) { + hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i])); + } + + status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector); + if (error != NO_ERROR) { + jniThrowExceptionFmt(env, "java/lang/SecurityException", + "ACCESS_SURFACE_FLINGER is missing"); + } +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sDisplayMethods[] = { @@ -41,6 +63,8 @@ static const JNINativeMethod sDisplayMethods[] = { (void*)nativeCreateDisplay }, {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", (void*)nativeDestroyDisplay }, + {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V", + (void*)nativeOverrideHdrTypes }, // clang-format on }; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bb6bd4aa1bbc..42dfee375ae4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5725,7 +5725,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { return setKeyChainGrantInternal(alias, hasGrant, granteeUid, caller.getUserHandle()); } catch (IllegalArgumentException e) { - if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, packageName, + if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, callerPackage, caller.getUserId())) { throw e; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f921ccf62a3b..7652d6df133a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -373,8 +373,6 @@ public final class SystemServer implements Dumpable { "com.android.server.searchui.SearchUiManagerService"; private static final String SMARTSPACE_MANAGER_SERVICE_CLASS = "com.android.server.smartspace.SmartspaceManagerService"; - private static final String CLOUDSEARCH_MANAGER_SERVICE_CLASS = - "com.android.server.cloudsearch.CloudSearchManagerService"; private static final String DEVICE_IDLE_CONTROLLER_CLASS = "com.android.server.DeviceIdleController"; private static final String BLOB_STORE_MANAGER_SERVICE_CLASS = @@ -1857,12 +1855,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS); t.traceEnd(); - // CloudSearch manager service - // TODO: add deviceHasConfigString(context, R.string.config_defaultCloudSearchServices) - t.traceBegin("StartCloudSearchService"); - mSystemServiceManager.startService(CLOUDSEARCH_MANAGER_SERVICE_CLASS); - t.traceEnd(); - t.traceBegin("InitConnectivityModuleConnector"); try { ConnectivityModuleConnector.getInstance().init(context); diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp index 1bcc3d1f70ad..1c6ba33bd279 100644 --- a/services/tests/PackageManagerServiceTests/unit/Android.bp +++ b/services/tests/PackageManagerServiceTests/unit/Android.bp @@ -35,6 +35,7 @@ android_test { "kotlin-reflect", "services.core", "servicestests-utils", + "servicestests-core-utils", "truth-prebuilt", ], platform_apis: true, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 5361041c4aff..b41fd39b355c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -23,7 +23,6 @@ import android.content.pm.FeatureGroupInfo import android.content.pm.FeatureInfo import android.content.pm.PackageManager import android.content.pm.SigningDetails -import com.android.server.pm.pkg.parsing.ParsingPackage import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -65,6 +64,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "addMimeGroupsFromComponent", "assignDerivedFields", "assignDerivedFields2", + "makeImmutable", "buildFakeForDeletion", "buildAppClassNamesByProcess", "capPermissionPriorities", @@ -219,6 +219,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::isNativeLibraryRootRequiresIsa, AndroidPackage::isOdm, AndroidPackage::isOem, + AndroidPackage::isOnBackInvokedCallbackEnabled, AndroidPackage::isOverlay, AndroidPackage::isOverlayIsStatic, AndroidPackage::isPartiallyDirectBootAware, @@ -279,7 +280,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"), getSetByValue( AndroidPackage::areAttributionsUserVisible, - ParsingPackage::setAttributionsAreUserVisible, + PackageImpl::setAttributionsAreUserVisible, true ), getSetByValue2( @@ -513,11 +514,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag } ), getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")), - getSetByValue( - AndroidPackage::isOnBackInvokedCallbackEnabled, - ParsingPackage::setOnBackInvokedCallbackEnabled, - true - ) ) override fun initialObject() = PackageImpl.forParsing( @@ -560,10 +556,13 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag .setSplitHasCode(1, false) .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero") .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne") - .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1")) .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2")) + override fun finalizeObject(parcelable: Parcelable) { + (parcelable as PackageImpl).hideAsParsed().hideAsFinal() + } + override fun extraAssertions(before: Parcelable, after: Parcelable) { super.extraAssertions(before, after) after as PackageImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt index e16a1871f8b8..37bb9354df1c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt @@ -293,12 +293,21 @@ abstract class ParcelableComponentTest( first?.let { property(it) } == second?.let { property(it) } } - @Test - fun valueComparison() { + fun buildBefore(): Pair<List<Param>, Parcelable> { val params = baseParams.mapNotNull(::buildParams) + extraParams().filterNotNull() val before = initialObject() params.forEach { it.setFunction(arrayOf(before, it.value())) } + finalizeObject(before) + return params to before + } + + protected open fun finalizeObject(parcelable: Parcelable) { + } + + @Test + fun valueComparison() { + val (params, before) = buildBefore() val parcel = Parcel.obtain() writeToParcel(parcel, before) @@ -307,6 +316,15 @@ abstract class ParcelableComponentTest( parcel.setDataPosition(0) + val baseline = initialObject() + finalizeObject(baseline) + + val baselineParcel = Parcel.obtain() + writeToParcel(baselineParcel, baseline) + + // Check that something substantial actually changed in the test object + expect.that(parcel.dataSize()).isGreaterThan(baselineParcel.dataSize()) + val after = creator.createFromParcel(parcel) expect.withMessage("Mismatched write and read data sizes") @@ -314,6 +332,7 @@ abstract class ParcelableComponentTest( .isEqualTo(dataSize) parcel.recycle() + baselineParcel.recycle() runAssertions(params, before, after) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt new file mode 100644 index 000000000000..7e9e433c29ac --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.pkg + +import android.content.Intent +import android.content.pm.overlay.OverlayPaths +import android.content.pm.PackageManager +import android.content.pm.PathPermission +import android.content.pm.SharedLibraryInfo +import android.content.pm.VersionedPackage +import android.os.PatternMatcher +import android.util.ArraySet +import com.android.server.pm.PackageSetting +import com.android.server.pm.PackageSettingBuilder +import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.pkg.AndroidPackage +import com.android.server.pm.pkg.PackageState +import com.android.server.pm.pkg.PackageStateImpl +import com.android.server.pm.pkg.PackageUserState +import com.android.server.pm.pkg.PackageUserStateImpl +import com.android.server.pm.pkg.component.ParsedActivity +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedPermission +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.pm.pkg.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedProcess +import com.android.server.pm.pkg.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProvider +import com.android.server.pm.pkg.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedService +import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest +import com.google.common.truth.Expect +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import kotlin.contracts.ExperimentalContracts +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KType +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.memberFunctions +import kotlin.reflect.full.starProjectedType + +class PackageStateTest { + + companion object { + private val IGNORED_TYPES = listOf( + "java.io.File", + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.CharSequence", + "java.lang.Character", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Short", + "java.lang.String", + "java.lang.Void", + ) + // STOPSHIP: Remove these and fix the implementations + private val IGNORED_FUNCTIONS = listOf( + ParsedActivity::getIntents, + ParsedActivity::getKnownActivityEmbeddingCerts, + ParsedActivity::getProperties, + ParsedInstrumentation::getIntents, + ParsedInstrumentation::getIntents, + ParsedInstrumentation::getProperties, + ParsedInstrumentation::getProperties, + ParsedPermission::getIntents, + ParsedPermission::getProperties, + ParsedPermissionGroup::getIntents, + ParsedPermissionGroup::getProperties, + ParsedProcess::getAppClassNamesByPackage, + ParsedProvider::getIntents, + ParsedProvider::getPathPermissions, + ParsedProvider::getProperties, + ParsedProvider::getUriPermissionPatterns, + ParsedService::getIntents, + ParsedService::getProperties, + SharedLibraryInfo::getAllCodePaths, + SharedLibraryInfo::getDependencies, + Intent::getCategories, + PackageUserState::getDisabledComponents, + PackageUserState::getEnabledComponents, + PackageUserState::getSharedLibraryOverlayPaths, + OverlayPaths::getOverlayPaths, + OverlayPaths::getResourceDirs, + ) + } + + @get:Rule + val tempFolder = TemporaryFolder() + + @get:Rule + val expect = Expect.create() + + private val collectionType = MutableCollection::class.starProjectedType + private val mapType = Map::class.starProjectedType + + @OptIn(ExperimentalContracts::class) + @Test + fun collectionImmutability() { + val seenTypes = mutableSetOf<KType>() + val (_, pkg) = AndroidPackageTest().buildBefore() + val packageState = PackageSettingBuilder() + .setPackage(pkg as AndroidPackage) + .setCodePath(tempFolder.newFile().path) + .build() + + fillMissingData(packageState, pkg as PackageImpl) + + visitType(seenTypes, emptyList(), PackageStateImpl.copy(packageState), + PackageState::class.starProjectedType) + visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType) + visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0), + PackageUserState::class.starProjectedType) + + // Don't check empties for defaults since their collections will always be empty + visitType(seenTypes, emptyList(), PackageUserState.DEFAULT, + PackageUserState::class.starProjectedType, enforceNonEmpty = false) + + // Check that some minimum number of functions were validated, + // in case the type checking breaks somehow + expect.that(seenTypes.size).isGreaterThan(10) + } + + /** + * Fill fields in [PackageState] and its children that are not filled by [AndroidPackageTest]. + * Real objects and real invocations of the live APIs are necessary to ensure that the test + * mirrors real device behavior. + */ + private fun fillMissingData(pkgSetting: PackageSetting, pkg: PackageImpl) { + pkgSetting.addUsesLibraryFile("usesLibraryFile") + pkgSetting.addUsesLibraryInfo(SharedLibraryInfo( + "path", + "packageName", + listOf(tempFolder.newFile().path), + "name", + 1, + 0, + VersionedPackage("versionedPackage0", 1), + listOf(VersionedPackage("versionedPackage1", 2)), + emptyList(), + false + )) + pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType")) + pkgSetting.getOrCreateUserState(0).apply { + setEnabledComponents(ArraySet<String>().apply { add("com.test.EnabledComponent") }) + setDisabledComponents(ArraySet<String>().apply { add("com.test.DisabledComponent") }) + setSharedLibraryOverlayPaths("sharedLibrary", + OverlayPaths.Builder().addApkPath("/test/overlay.apk").build()) + } + + val property = PackageManager.Property("propertyName", 1, "com.test", null) + listOf( + pkg.activities, + pkg.receivers, + pkg.providers, + pkg.services, + pkg.instrumentations, + pkg.permissions, + pkg.permissionGroups + ).map { it.first() as ParsedComponentImpl } + .forEach { + it.addIntent(ParsedIntentInfoImpl()) + it.addProperty(property) + } + + (pkg.activities.first() as ParsedActivityImpl).knownActivityEmbeddingCerts = + setOf("TESTEMBEDDINGCERT") + + (pkg.permissions.first() as ParsedPermissionImpl).knownCerts = setOf("TESTEMBEDDINGCERT") + + (pkg.providers.first() as ParsedProviderImpl).apply { + addPathPermission(PathPermission("pattern", PatternMatcher.PATTERN_LITERAL, + "readPermission", "writerPermission")) + addUriPermissionPattern(PatternMatcher("*", PatternMatcher.PATTERN_LITERAL)) + } + + (pkg.processes.values.first() as ParsedProcessImpl).apply { + deniedPermissions = setOf("deniedPermission") + putAppClassNameForPackage("package", "className") + } + } + + private fun visitType( + seenTypes: MutableSet<KType>, + parentChain: List<String>, + impl: Any, + type: KType, + enforceNonEmpty: Boolean = true + ) { + if (!seenTypes.add(type)) return + val kClass = type.classifier as KClass<*> + val qualifiedName = kClass.qualifiedName!! + if (IGNORED_TYPES.contains(qualifiedName)) return + + val newChain = parentChain + kClass.simpleName!! + val newChainText = newChain.joinToString() + + val filteredFunctions = kClass.memberFunctions + .filter { + // Size 1 because the impl receiver counts as a parameter + it.parameters.size == 1 + } + .filterNot(IGNORED_FUNCTIONS::contains) + + filteredFunctions.filter { it.returnType.isSubtypeOf(collectionType) } + .forEach { + val collection = it.call(impl) + if (collection as? MutableCollection<*> == null) { + expect.withMessage("Method $newChainText ${it.name} cannot return null") + .fail() + return@forEach + } + + val value = try { + collection.stream().findFirst().get()!! + } catch (e: Exception) { + if (enforceNonEmpty) { + expect.withMessage("Method $newChainText ${it.name} returns empty") + .that(e) + .isNull() + return@forEach + } else null + } + + if (value != null) { + it.returnType.arguments.forEach { + visitType(seenTypes, newChain, value, it.type!!) + } + } + + // Must test clear last in case it works and actually clears the collection + expectUnsupported(newChain, it) { collection.clear() } + } + filteredFunctions.filter { it.returnType.isSubtypeOf(mapType) } + .forEach { + val map = it.call(impl) + if (map as? MutableMap<*, *> == null) { + expect.withMessage("Method $newChainText ${it.name} cannot return null") + .fail() + return@forEach + } + + val entry = try { + map.entries.stream().findFirst().get()!! + } catch (e: Exception) { + expect.withMessage("Method $newChainText ${it.name} returns empty") + .that(e) + .isNull() + return@forEach + } + + visitType(seenTypes, newChain, entry.key!!, it.returnType.arguments[0].type!!) + visitType(seenTypes, newChain, entry.value!!, it.returnType.arguments[1].type!!) + + // Must test clear last in case it works and actually clears the map + expectUnsupported(newChain, it) { map.clear() } + } + } + + private fun expectUnsupported( + parentChain: List<String>, + function: KFunction<*>, + block: () -> Unit + ) { + val exception = try { + block() + null + } catch (e: UnsupportedOperationException) { + e + } + + expect.withMessage("Method ${parentChain.joinToString()} $function doesn't throw") + .that(exception) + .isNotNull() + } +}
\ No newline at end of file diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 07b763dcd85b..33ac73560968 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -34,10 +34,15 @@ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> <uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY" /> <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> <!-- needed by MasterClearReceiverTest to display a system dialog --> <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/> + <!-- needed by TrustManagerServiceTest to access LockSettings' secure storage --> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <application android:testOnly="true" android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java new file mode 100644 index 000000000000..e09b80e196b1 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList; +import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList; +import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN; +import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE; +import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN; +import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED; +import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW; +import static com.android.server.am.BroadcastQueueTest.getUidForPackage; +import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; + +import android.annotation.NonNull; +import android.app.Activity; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; + +@SmallTest +@RunWith(MockitoJUnitRunner.class) +public class BroadcastQueueModernImplTest { + private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID; + + @Mock ActivityManagerService mAms; + + @Mock BroadcastProcessQueue mQueue1; + @Mock BroadcastProcessQueue mQueue2; + @Mock BroadcastProcessQueue mQueue3; + @Mock BroadcastProcessQueue mQueue4; + + HandlerThread mHandlerThread; + + BroadcastConstants mConstants; + BroadcastQueueModernImpl mImpl; + + BroadcastProcessQueue mHead; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + + mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); + mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), + mConstants, mConstants); + + doReturn(1L).when(mQueue1).getRunnableAt(); + doReturn(2L).when(mQueue2).getRunnableAt(); + doReturn(3L).when(mQueue3).getRunnableAt(); + doReturn(4L).when(mQueue4).getRunnableAt(); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + } + + private static void assertOrphan(BroadcastProcessQueue queue) { + assertNull(queue.runnableAtNext); + assertNull(queue.runnableAtPrev); + } + + private static void assertRunnableList(@NonNull List<BroadcastProcessQueue> expected, + @NonNull BroadcastProcessQueue actualHead) { + BroadcastProcessQueue test = actualHead; + final int N = expected.size(); + for (int i = 0; i < N; i++) { + final BroadcastProcessQueue expectedPrev = (i > 0) ? expected.get(i - 1) : null; + final BroadcastProcessQueue expectedTest = expected.get(i); + final BroadcastProcessQueue expectedNext = (i < N - 1) ? expected.get(i + 1) : null; + + assertEquals("prev", expectedPrev, test.runnableAtPrev); + assertEquals("test", expectedTest, test); + assertEquals("next", expectedNext, test.runnableAtNext); + + test = test.runnableAtNext; + } + if (N == 0) { + assertNull(actualHead); + } + } + + private BroadcastRecord makeBroadcastRecord(Intent intent) { + return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false); + } + + private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) { + return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true); + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) { + return makeBroadcastRecord(intent, options, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false); + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options, + List receivers, boolean ordered) { + return new BroadcastRecord(mImpl, intent, null, PACKAGE_RED, null, 21, 42, false, null, + null, null, null, AppOpsManager.OP_NONE, options, receivers, null, + Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM, + false, null, false, null); + } + + @Test + public void testRunnableList_Simple() { + assertRunnableList(List.of(), mHead); + + mHead = insertIntoRunnableList(mHead, mQueue1); + assertRunnableList(List.of(mQueue1), mHead); + + mHead = removeFromRunnableList(mHead, mQueue1); + assertRunnableList(List.of(), mHead); + } + + @Test + public void testRunnableList_InsertLast() { + mHead = insertIntoRunnableList(mHead, mQueue1); + mHead = insertIntoRunnableList(mHead, mQueue2); + mHead = insertIntoRunnableList(mHead, mQueue3); + mHead = insertIntoRunnableList(mHead, mQueue4); + assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead); + } + + @Test + public void testRunnableList_InsertFirst() { + mHead = insertIntoRunnableList(mHead, mQueue4); + mHead = insertIntoRunnableList(mHead, mQueue3); + mHead = insertIntoRunnableList(mHead, mQueue2); + mHead = insertIntoRunnableList(mHead, mQueue1); + assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead); + } + + @Test + public void testRunnableList_InsertMiddle() { + mHead = insertIntoRunnableList(mHead, mQueue1); + mHead = insertIntoRunnableList(mHead, mQueue3); + mHead = insertIntoRunnableList(mHead, mQueue2); + assertRunnableList(List.of(mQueue1, mQueue2, mQueue3), mHead); + } + + @Test + public void testRunnableList_Remove() { + mHead = insertIntoRunnableList(mHead, mQueue1); + mHead = insertIntoRunnableList(mHead, mQueue2); + mHead = insertIntoRunnableList(mHead, mQueue3); + mHead = insertIntoRunnableList(mHead, mQueue4); + + mHead = removeFromRunnableList(mHead, mQueue3); + assertRunnableList(List.of(mQueue1, mQueue2, mQueue4), mHead); + + mHead = removeFromRunnableList(mHead, mQueue1); + assertRunnableList(List.of(mQueue2, mQueue4), mHead); + + mHead = removeFromRunnableList(mHead, mQueue4); + assertRunnableList(List.of(mQueue2), mHead); + + mHead = removeFromRunnableList(mHead, mQueue2); + assertRunnableList(List.of(), mHead); + + // Verify all links cleaned up during removal + assertOrphan(mQueue1); + assertOrphan(mQueue2); + assertOrphan(mQueue3); + assertOrphan(mQueue4); + } + + @Test + public void testProcessQueue_Complex() { + BroadcastProcessQueue red = mImpl.getOrCreateProcessQueue(PACKAGE_RED, TEST_UID); + BroadcastProcessQueue green = mImpl.getOrCreateProcessQueue(PACKAGE_GREEN, TEST_UID); + BroadcastProcessQueue blue = mImpl.getOrCreateProcessQueue(PACKAGE_BLUE, TEST_UID); + + assertEquals(PACKAGE_RED, red.processName); + assertEquals(PACKAGE_GREEN, green.processName); + assertEquals(PACKAGE_BLUE, blue.processName); + + // Verify that removing middle queue works + mImpl.removeProcessQueue(PACKAGE_GREEN, TEST_UID); + assertEquals(red, mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); + assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); + + // Verify that removing head queue works + mImpl.removeProcessQueue(PACKAGE_RED, TEST_UID); + assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); + assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); + + // Verify that removing last queue works + mImpl.removeProcessQueue(PACKAGE_BLUE, TEST_UID); + assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); + assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); + + // Verify that removing missing doesn't crash + mImpl.removeProcessQueue(PACKAGE_YELLOW, TEST_UID); + + // Verify that we can start all over again safely + BroadcastProcessQueue yellow = mImpl.getOrCreateProcessQueue(PACKAGE_YELLOW, TEST_UID); + assertEquals(yellow, mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); + } + + /** + * Empty queue isn't runnable. + */ + @Test + public void testRunnableAt_Empty() { + BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + assertFalse(queue.isRunnable()); + assertEquals(Long.MAX_VALUE, queue.getRunnableAt()); + } + + /** + * Queue with a "normal" broadcast is runnable at different times depending + * on process cached state; when cached it's delayed by some amount. + */ + @Test + public void testRunnableAt_Normal() { + BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); + queue.enqueueBroadcast(airplaneRecord, 0); + + queue.setProcessCached(false); + final long notCachedRunnableAt = queue.getRunnableAt(); + queue.setProcessCached(true); + final long cachedRunnableAt = queue.getRunnableAt(); + assertTrue(cachedRunnableAt > notCachedRunnableAt); + } + + /** + * Queue with foreground broadcast is always runnable immediately, + * regardless of process cached state. + */ + @Test + public void testRunnableAt_Foreground() { + BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); + queue.enqueueBroadcast(airplaneRecord, 0); + + queue.setProcessCached(false); + assertTrue(queue.isRunnable()); + assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt()); + + queue.setProcessCached(true); + assertTrue(queue.isRunnable()); + assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt()); + } + + /** + * Verify that sending a broadcast that removes any matching pending + * broadcasts is applied as expected. + */ + @Test + public void testRemoveMatchingFilter() { + final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON); + final BroadcastOptions optionsOn = BroadcastOptions.makeBasic(); + optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF)); + + final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF); + final BroadcastOptions optionsOff = BroadcastOptions.makeBasic(); + optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON)); + + // Halt all processing so that we get a consistent view + mHandlerThread.getLooper().getQueue().postSyncBarrier(); + + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn)); + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff)); + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn)); + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff)); + + // Marching through the queue we should only have one SCREEN_OFF + // broadcast, since that's the last state we dispatched + final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction()); + assertTrue(queue.isEmpty()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 6bf102a09368..cf5d1133b741 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -16,22 +16,34 @@ package com.android.server.am; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; +import android.app.usage.UsageEvents.Event; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.IIntentReceiver; @@ -39,12 +51,15 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.PowerExemptionManager; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -58,6 +73,7 @@ import com.android.server.am.ActivityManagerService.Injector; import com.android.server.appop.AppOpsService; import com.android.server.wm.ActivityTaskManagerService; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -65,14 +81,23 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.mockito.ArgumentMatcher; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; /** * Common tests for {@link BroadcastQueue} implementations. @@ -104,6 +129,8 @@ public class BroadcastQueueTest { private ProcessList mProcessList; @Mock private PackageManagerInternal mPackageManagerInt; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInt; private ActivityManagerService mAms; private BroadcastQueue mQueue; @@ -113,6 +140,11 @@ public class BroadcastQueueTest { */ private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>(); + /** + * Collection of all active processes during current test run. + */ + private List<ProcessRecord> mActiveProcesses = new ArrayList<>(); + @Parameters(name = "impl={0}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} }); @@ -136,20 +168,27 @@ public class BroadcastQueueTest { LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); + doAnswer((invocation) -> { + return getUidForPackage(invocation.getArgument(0)); + }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); final ActivityManagerService realAms = new ActivityManagerService( new TestInjector(mContext), mServiceThreadRule.getThread()); realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); + realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer); realAms.mPackageManagerInt = mPackageManagerInt; + realAms.mUsageStatsService = mUsageStatsManagerInt; + realAms.mProcessesReady = true; mAms = spy(realAms); doAnswer((invocation) -> { Log.v(TAG, "Intercepting startProcessLocked() for " + Arrays.toString(invocation.getArguments())); final String processName = invocation.getArgument(0); final ApplicationInfo ai = invocation.getArgument(1); - final ProcessRecord res = makeActiveProcessRecord(ai, processName); + final ProcessRecord res = makeActiveProcessRecord(ai, processName, false, + false, UnaryOperator.identity()); mHandlerThread.getThreadHandler().post(() -> { synchronized (mAms) { mQueue.onApplicationAttachedLocked(res); @@ -158,9 +197,12 @@ public class BroadcastQueueTest { return res; }).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(), any(), anyInt(), anyBoolean(), anyBoolean()); + doNothing().when(mAms).appNotResponding(any(), any()); final BroadcastConstants constants = new BroadcastConstants( Settings.Global.BROADCAST_FG_CONSTANTS); + constants.TIMEOUT = 100; + constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) { public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) { return false; @@ -180,12 +222,24 @@ public class BroadcastQueueTest { ProcessList.SCHED_GROUP_DEFAULT); } else if (mImpl == Impl.MODERN) { mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), - constants, emptySkipPolicy, emptyHistory); + constants, constants, emptySkipPolicy, emptyHistory); } else { throw new UnsupportedOperationException(); } } + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + + // Verify that all processes have finished handling broadcasts + for (ProcessRecord app : mActiveProcesses) { + assertTrue(app.toShortString(), app.mReceivers.numberOfCurReceivers() == 0); + assertTrue(app.toShortString(), mQueue.getPreferredSchedulingGroupLocked(app) + == ProcessList.SCHED_GROUP_UNDEFINED); + } + } + private class TestInjector extends Injector { TestInjector(Context context) { super(context); @@ -207,15 +261,40 @@ public class BroadcastQueueTest { } } + /** + * Helper that leverages try-with-resources to pause dispatch of + * {@link #mHandlerThread} until released. + */ + private class SyncBarrier implements AutoCloseable { + private final int mToken; + + public SyncBarrier() { + mToken = mHandlerThread.getLooper().getQueue().postSyncBarrier(); + } + + @Override + public void close() throws Exception { + mHandlerThread.getLooper().getQueue().removeSyncBarrier(mToken); + } + } + private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception { final ApplicationInfo ai = makeApplicationInfo(packageName); - return makeActiveProcessRecord(ai, ai.processName); + return makeActiveProcessRecord(ai, ai.processName, false, false, + UnaryOperator.identity()); } - private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName) - throws Exception { - final ProcessRecord r = new ProcessRecord(mAms, ai, processName, ai.uid); + private ProcessRecord makeWedgedActiveProcessRecord(String packageName) throws Exception { + final ApplicationInfo ai = makeApplicationInfo(packageName); + return makeActiveProcessRecord(ai, ai.processName, true, false, + UnaryOperator.identity()); + } + + private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName, + boolean wedged, boolean abort, UnaryOperator<Bundle> extrasOperator) throws Exception { + final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid)); r.setPid(mNextPid.getAndIncrement()); + mActiveProcesses.add(r); final IApplicationThread thread = mock(IApplicationThread.class); final IBinder threadBinder = new Binder(); @@ -233,12 +312,18 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleReceiver() for " + Arrays.toString(invocation.getArguments())); - mHandlerThread.getThreadHandler().post(() -> { - synchronized (mAms) { - mQueue.finishReceiverLocked(r, Activity.RESULT_OK, - null, null, false, false); - } - }); + final Bundle extras = invocation.getArgument(5); + if (!wedged) { + assertTrue(r.mReceivers.numberOfCurReceivers() > 0); + assertTrue(mQueue.getPreferredSchedulingGroupLocked(r) + != ProcessList.SCHED_GROUP_UNDEFINED); + mHandlerThread.getThreadHandler().post(() -> { + synchronized (mAms) { + mQueue.finishReceiverLocked(r, Activity.RESULT_OK, + null, extrasOperator.apply(extras), abort, false); + } + }); + } return null; }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(), anyInt(), anyInt()); @@ -246,12 +331,16 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " + Arrays.toString(invocation.getArguments())); + final Bundle extras = invocation.getArgument(4); final boolean ordered = invocation.getArgument(5); - if (ordered) { + if (!wedged && ordered) { + assertTrue(r.mReceivers.numberOfCurReceivers() > 0); + assertTrue(mQueue.getPreferredSchedulingGroupLocked(r) + != ProcessList.SCHED_GROUP_UNDEFINED); mHandlerThread.getThreadHandler().post(() -> { synchronized (mAms) { mQueue.finishReceiverLocked(r, Activity.RESULT_OK, - null, null, false, false); + null, extrasOperator.apply(extras), abort, false); } }); } @@ -262,7 +351,7 @@ public class BroadcastQueueTest { return r; } - private ApplicationInfo makeApplicationInfo(String packageName) { + static ApplicationInfo makeApplicationInfo(String packageName) { final ApplicationInfo ai = new ApplicationInfo(); ai.packageName = packageName; ai.processName = packageName; @@ -270,7 +359,7 @@ public class BroadcastQueueTest { return ai; } - private ResolveInfo makeManifestReceiver(String packageName, String name) { + static ResolveInfo makeManifestReceiver(String packageName, String name) { final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); ri.activityInfo.packageName = packageName; @@ -292,15 +381,35 @@ public class BroadcastQueueTest { private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, List receivers) { - return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), receivers); + return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), + receivers, false, null, null); + } + + private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp, + List receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) { + return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), + receivers, true, orderedResultTo, orderedExtras); } private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, BroadcastOptions options, List receivers) { + return makeBroadcastRecord(intent, callerApp, options, receivers, false, null, null); + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, + BroadcastOptions options, List receivers, boolean ordered, + IIntentReceiver orderedResultTo, Bundle orderedExtras) { return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false, null, null, null, null, - AppOpsManager.OP_NONE, options, receivers, null, Activity.RESULT_OK, null, null, - false, false, false, UserHandle.USER_SYSTEM, false, null, false, null); + AppOpsManager.OP_NONE, options, receivers, orderedResultTo, Activity.RESULT_OK, + null, orderedExtras, ordered, false, false, UserHandle.USER_SYSTEM, false, null, + false, null); + } + + private ArgumentMatcher<Intent> filterEquals(Intent intent) { + return (test) -> { + return intent.filterEquals(test); + }; } private ArgumentMatcher<Intent> filterEqualsIgnoringComponent(Intent intent) { @@ -313,6 +422,17 @@ public class BroadcastQueueTest { }; } + private ArgumentMatcher<Bundle> bundleEquals(Bundle bundle) { + return (test) -> { + // TODO: check values in addition to keys + return Objects.equals(test.keySet(), bundle.keySet()); + }; + } + + private @NonNull Bundle clone(@Nullable Bundle b) { + return (b != null) ? new Bundle(b) : new Bundle(); + } + private void enqueueBroadcast(BroadcastRecord r) { synchronized (mAms) { mQueue.enqueueBroadcastLocked(r); @@ -329,6 +449,15 @@ public class BroadcastQueueTest { any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); } + private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent, + ComponentName component) throws Exception { + final Intent targetedIntent = new Intent(intent); + targetedIntent.setComponent(component); + verify(app.getThread(), mode).scheduleReceiver( + argThat(filterEquals(targetedIntent)), any(), any(), anyInt(), any(), + any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + } + private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent) throws Exception { verify(app.getThread()).scheduleRegisteredReceiver(any(), @@ -336,17 +465,19 @@ public class BroadcastQueueTest { anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); } - private static final String PACKAGE_RED = "com.example.red"; - private static final String PACKAGE_GREEN = "com.example.green"; - private static final String PACKAGE_BLUE = "com.example.blue"; - private static final String PACKAGE_YELLOW = "com.example.yellow"; + static final int USER_GUEST = 11; - private static final String CLASS_RED = "com.example.red.Red"; - private static final String CLASS_GREEN = "com.example.green.Green"; - private static final String CLASS_BLUE = "com.example.blue.Blue"; - private static final String CLASS_YELLOW = "com.example.yellow.Yellow"; + static final String PACKAGE_RED = "com.example.red"; + static final String PACKAGE_GREEN = "com.example.green"; + static final String PACKAGE_BLUE = "com.example.blue"; + static final String PACKAGE_YELLOW = "com.example.yellow"; - private static int getUidForPackage(String packageName) { + static final String CLASS_RED = "com.example.red.Red"; + static final String CLASS_GREEN = "com.example.green.Green"; + static final String CLASS_BLUE = "com.example.blue.Blue"; + static final String CLASS_YELLOW = "com.example.yellow.Yellow"; + + static int getUidForPackage(@NonNull String packageName) { switch (packageName) { case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1; case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2; @@ -356,6 +487,14 @@ public class BroadcastQueueTest { } } + @Test + public void testDump() throws Exception { + // To maximize test coverage, dump current state; we're not worried + // about the actual output, just that we don't crash + mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()), + null, 0, true, null, false); + } + /** * Verify dispatch of simple broadcast to single manifest receiver in * already-running warm app. @@ -491,7 +630,10 @@ public class BroadcastQueueTest { makeRegisteredReceiver(receiverYellowApp)))); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + airplane.setComponent(new ComponentName(PACKAGE_YELLOW, CLASS_YELLOW)); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.recordResponseEventWhileInBackground(42L); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options, List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)))); waitForIdle(); @@ -502,5 +644,304 @@ public class BroadcastQueueTest { verifyScheduleReceiver(receiverBlueApp, timezone); verifyScheduleRegisteredReceiver(receiverYellowApp, timezone); verifyScheduleReceiver(receiverYellowApp, airplane); + + for (ProcessRecord receiverApp : new ProcessRecord[] { + receiverGreenApp, receiverBlueApp, receiverYellowApp + }) { + // Confirm expected OOM adjustments; we were invoked once to upgrade + // and once to downgrade + assertEquals(ActivityManager.PROCESS_STATE_RECEIVER, + receiverApp.mState.getReportedProcState()); + verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp)); + + if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) { + // Nuance: the default implementation doesn't ask for manifest + // cold-started apps to be thawed, but the modern stack does + } else { + // Confirm that app was thawed + verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp), + eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER)); + + // Confirm that we added package to process + verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName), + anyLong(), any()); + } + + // Confirm that we've reported package as being used + verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName), + eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER)); + + // Confirm that we unstopped manifest receivers + verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState( + eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM)); + } + + // Confirm that we've reported expected usage events + verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid), + eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt()); + verify(mAms.mUsageStatsService).reportEvent(eq(PACKAGE_YELLOW), eq(UserHandle.USER_SYSTEM), + eq(Event.APP_COMPONENT_USED)); + } + + /** + * Verify that we detect and ANR a wedged process. + */ + @Test + public void testWedged() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeWedgedActiveProcessRecord(PACKAGE_GREEN); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))); + + waitForIdle(); + verify(mAms).appNotResponding(eq(receiverApp), any()); + } + + /** + * Verify that we cleanup a disabled component, skipping a pending dispatch + * of broadcast to that component. + */ + @Test + public void testCleanup() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + try (SyncBarrier b = new SyncBarrier()) { + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>( + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_RED), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE))))); + + synchronized (mAms) { + mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN), + UserHandle.USER_SYSTEM); + + // Also try clearing out other unrelated things that should leave + // the final receiver intact + mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null, + UserHandle.USER_SYSTEM); + mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST); + } + + // To maximize test coverage, dump current state; we're not worried + // about the actual output, just that we don't crash + mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()), + null, 0, true, null, false); + } + + waitForIdle(); + verifyScheduleReceiver(times(1), receiverApp, airplane, + new ComponentName(PACKAGE_GREEN, CLASS_RED)); + verifyScheduleReceiver(never(), receiverApp, airplane, + new ComponentName(PACKAGE_GREEN, CLASS_GREEN)); + verifyScheduleReceiver(times(1), receiverApp, airplane, + new ComponentName(PACKAGE_GREEN, CLASS_BLUE)); + } + + /** + * Verify that we skip broadcasts to an app being backed up. + */ + @Test + public void testBackup() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + receiverApp.setInFullBackup(true); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))); + + waitForIdle(); + verifyScheduleReceiver(never(), receiverApp, airplane, + new ComponentName(PACKAGE_GREEN, CLASS_GREEN)); + } + + /** + * Verify that an ordered broadcast collects results from everyone along the + * chain, and is delivered to final destination. + */ + @Test + public void testOrdered() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + // Purposefully warm-start the middle apps to make sure we dispatch to + // both cold and warm apps in expected order + makeActiveProcessRecord(makeApplicationInfo(PACKAGE_BLUE), PACKAGE_BLUE, + false, false, (extras) -> { + extras = clone(extras); + extras.putBoolean(PACKAGE_BLUE, true); + return extras; + }); + makeActiveProcessRecord(makeApplicationInfo(PACKAGE_YELLOW), PACKAGE_YELLOW, + false, false, (extras) -> { + extras = clone(extras); + extras.putBoolean(PACKAGE_YELLOW, true); + return extras; + }); + + final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class); + final Bundle orderedExtras = new Bundle(); + orderedExtras.putBoolean(PACKAGE_RED, true); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)), + orderedResultTo, orderedExtras)); + + waitForIdle(); + final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)).getThread(); + final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE, + getUidForPackage(PACKAGE_BLUE)).getThread(); + final IApplicationThread yellowThread = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)).getThread(); + final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)).getThread(); + + // Verify that we called everyone in specific order, and that each of + // them observed the expected extras at that stage + final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread); + final Bundle expectedExtras = new Bundle(); + expectedExtras.putBoolean(PACKAGE_RED, true); + inOrder.verify(greenThread).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), + eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), + eq(UserHandle.USER_SYSTEM), anyInt()); + inOrder.verify(blueThread).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), + eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), + eq(UserHandle.USER_SYSTEM), anyInt()); + expectedExtras.putBoolean(PACKAGE_BLUE, true); + inOrder.verify(yellowThread).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), + eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true), + eq(UserHandle.USER_SYSTEM), anyInt()); + expectedExtras.putBoolean(PACKAGE_YELLOW, true); + inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)), + eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(false), + anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + + // Finally, verify that we thawed the final receiver + verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp), + eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER)); + } + + /** + * Verify that an ordered broadcast can be aborted partially through + * dispatch, and is then delivered to final destination. + */ + @Test + public void testOrdered_Aborting() throws Exception { + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + doOrdered_Aborting(airplane); + } + + /** + * Verify that an ordered broadcast marked with + * {@link Intent#FLAG_RECEIVER_NO_ABORT} cannot be aborted partially through + * dispatch, and is delivered to everyone in order. + */ + @Test + public void testOrdered_Aborting_NoAbort() throws Exception { + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + airplane.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); + doOrdered_Aborting(airplane); + } + + public void doOrdered_Aborting(@NonNull Intent intent) throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + // Create a process that aborts any ordered broadcasts + makeActiveProcessRecord(makeApplicationInfo(PACKAGE_GREEN), PACKAGE_GREEN, + false, true, (extras) -> { + extras = clone(extras); + extras.putBoolean(PACKAGE_GREEN, true); + return extras; + }); + makeActiveProcessRecord(PACKAGE_BLUE); + + final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class); + + enqueueBroadcast(makeOrderedBroadcastRecord(intent, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), + orderedResultTo, null)); + + waitForIdle(); + final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)).getThread(); + final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE, + getUidForPackage(PACKAGE_BLUE)).getThread(); + final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)).getThread(); + + final Bundle expectedExtras = new Bundle(); + expectedExtras.putBoolean(PACKAGE_GREEN, true); + + // Verify that we always invoke the first receiver, but then we might + // have invoked or skipped the second receiver depending on the intent + // flag policy; we always deliver to final receiver regardless of abort + final InOrder inOrder = inOrder(greenThread, blueThread, redThread); + inOrder.verify(greenThread).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(intent)), any(), any(), + eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM), + anyInt()); + if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) { + inOrder.verify(blueThread).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(intent)), any(), any(), + eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM), + anyInt()); + } else { + inOrder.verify(blueThread, never()).scheduleReceiver(any(), any(), any(), anyInt(), + any(), any(), anyBoolean(), anyInt(), anyInt()); + } + inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(intent)), + eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), + eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + } + + @Test + public void testBackgroundActivityStarts() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Binder backgroundActivityStartsToken = new Binder(); + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final BroadcastRecord r = new BroadcastRecord(mQueue, intent, callerApp, + callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false, + null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(), + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, Activity.RESULT_OK, + null, null, false, false, false, UserHandle.USER_SYSTEM, true, + backgroundActivityStartsToken, false, null); + enqueueBroadcast(r); + + waitForIdle(); + verify(receiverApp).addOrUpdateAllowBackgroundActivityStartsToken(eq(r), + eq(backgroundActivityStartsToken)); + verify(receiverApp).removeAllowBackgroundActivityStartsToken(eq(r)); + } + + @Test + public void testOptions_TemporaryAppAllowlist() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppAllowlist(1_000, + PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + PowerExemptionManager.REASON_VPN, TAG); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))); + + waitForIdle(); + verify(mAms).tempAllowlistUidLocked(eq(receiverApp.uid), eq(1_000L), + eq(options.getTemporaryAppAllowlistReasonCode()), any(), + eq(options.getTemporaryAppAllowlistType()), eq(callerApp.uid)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java index c4c3abc1388e..145e66c92f14 100644 --- a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java @@ -30,10 +30,13 @@ import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraInjectionSession; import android.hardware.camera2.CameraManager; import android.os.Process; +import android.os.UserManager; import android.testing.TestableContext; import android.util.ArraySet; @@ -51,12 +54,17 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidJUnit4.class) public class CameraAccessControllerTest { private static final String FRONT_CAMERA = "0"; private static final String REAR_CAMERA = "1"; private static final String TEST_APP_PACKAGE = "some.package"; private static final String OTHER_APP_PACKAGE = "other.package"; + private static final int PERSONAL_PROFILE_USER_ID = 0; + private static final int WORK_PROFILE_USER_ID = 10; private CameraAccessController mController; @@ -69,6 +77,8 @@ public class CameraAccessControllerTest { @Mock private PackageManager mPackageManager; @Mock + private UserManager mUserManager; + @Mock private VirtualDeviceManagerInternal mDeviceManagerInternal; @Mock private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback; @@ -76,6 +86,7 @@ public class CameraAccessControllerTest { private ApplicationInfo mTestAppInfo = new ApplicationInfo(); private ApplicationInfo mOtherAppInfo = new ApplicationInfo(); private ArraySet<Integer> mRunningUids = new ArraySet<>(); + private List<UserInfo> mAliveUsers = new ArrayList<>(); @Captor ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor; @@ -84,6 +95,7 @@ public class CameraAccessControllerTest { public void setUp() throws PackageManager.NameNotFoundException { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(CameraManager.class, mCameraManager); + mContext.addMockSystemService(UserManager.class, mUserManager); mContext.setMockPackageManager(mPackageManager); LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal); @@ -92,10 +104,14 @@ public class CameraAccessControllerTest { mTestAppInfo.uid = Process.FIRST_APPLICATION_UID; mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1; mRunningUids.add(Process.FIRST_APPLICATION_UID); - when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn( - mTestAppInfo); - when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn( - mOtherAppInfo); + mAliveUsers.add(new UserInfo(PERSONAL_PROFILE_USER_ID, "", 0)); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + anyInt())).thenReturn(mTestAppInfo); + when(mPackageManager.getApplicationInfoAsUser( + eq(OTHER_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + anyInt())).thenReturn(mOtherAppInfo); + when(mUserManager.getAliveUsers()).thenReturn(mAliveUsers); mController.startObservingIfNeeded(); } @@ -227,4 +243,74 @@ public class CameraAccessControllerTest { verify(mCameraManager, times(1)).injectCamera(any(), any(), any(), any(), any()); } + + @Test + public void multipleUsers_getPersonalProfileAppUid_cameraBlocked() + throws CameraAccessException, NameNotFoundException { + mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0)); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(PERSONAL_PROFILE_USER_ID))).thenReturn(mTestAppInfo); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(WORK_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class); + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), any()); + } + + @Test + public void multipleUsers_getPersonalProfileAppUid_noCameraBlocking() + throws CameraAccessException, NameNotFoundException { + mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0)); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(PERSONAL_PROFILE_USER_ID))).thenReturn(mTestAppInfo); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(WORK_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class); + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(false); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + + verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any()); + } + + @Test + public void multipleUsers_getWorkProfileAppUid_cameraBlocked() + throws CameraAccessException, NameNotFoundException { + mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0)); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(PERSONAL_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(WORK_PROFILE_USER_ID))).thenReturn(mTestAppInfo); + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), any()); + } + + @Test + public void multipleUsers_getWorkProfileAppUid_noCameraBlocking() + throws CameraAccessException, NameNotFoundException { + mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0)); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(PERSONAL_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class); + when(mPackageManager.getApplicationInfoAsUser( + eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES), + eq(WORK_PROFILE_USER_ID))).thenReturn(mTestAppInfo); + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(false); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + + verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any()); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java new file mode 100644 index 000000000000..1a5d496f2002 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.Handler; +import android.os.PowerManager; +import android.os.test.TestLooper; +import android.util.FloatProperty; +import android.view.Display; +import android.view.DisplayInfo; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.display.RampAnimator.DualRampAnimator; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.testutils.OffsettableClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class DisplayPowerController2Test { + private static final String UNIQUE_DISPLAY_ID = "unique_id_test123"; + private static final int DISPLAY_ID = 42; + + private OffsettableClock mClock; + private TestLooper mTestLooper; + private Handler mHandler; + private DisplayPowerController2.Injector mInjector; + private Context mContextSpy; + + @Mock + private DisplayPowerCallbacks mDisplayPowerCallbacksMock; + @Mock + private SensorManager mSensorManagerMock; + @Mock + private DisplayBlanker mDisplayBlankerMock; + @Mock + private LogicalDisplay mLogicalDisplayMock; + @Mock + private DisplayDevice mDisplayDeviceMock; + @Mock + private BrightnessTracker mBrightnessTrackerMock; + @Mock + private BrightnessSetting mBrightnessSettingMock; + @Mock + private WindowManagerPolicy mWindowManagerPolicyMock; + @Mock + private PowerManager mPowerManagerMock; + @Mock + private Resources mResourcesMock; + @Mock + private DisplayDeviceConfig mDisplayDeviceConfigMock; + @Mock + private DisplayPowerState mDisplayPowerStateMock; + @Mock + private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock; + + @Captor + private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + mHandler = new Handler(mTestLooper.getLooper()); + mInjector = new DisplayPowerController2.Injector() { + @Override + DisplayPowerController2.Clock getClock() { + return mClock::now; + } + + @Override + DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, + int displayId, int displayState) { + return mDisplayPowerStateMock; + } + + @Override + DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps, + FloatProperty<DisplayPowerState> firstProperty, + FloatProperty<DisplayPowerState> secondProperty) { + return mDualRampAnimatorMock; + } + }; + + addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); + + when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock); + when(mContextSpy.getResources()).thenReturn(mResourcesMock); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(WindowManagerPolicy.class); + } + + @Test + public void testReleaseProxSuspendBlockersOnExit() throws Exception { + setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID); + + Sensor proxSensor = setUpProxSensor(); + + DisplayPowerController2 dpc = new DisplayPowerController2( + mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler, + mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock, + mBrightnessTrackerMock, mBrightnessSettingMock, () -> { + }); + + when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON); + // send a display power request + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + dpr.useProximitySensor = true; + dpc.requestPowerState(dpr, false /* waitForNegativeProximity */); + + // Run updatePowerState to start listener for the prox sensor + advanceTime(1); + + SensorEventListener listener = getSensorEventListener(proxSensor); + assertNotNull(listener); + + listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */)); + advanceTime(1); + + // two times, one for unfinished business and one for proximity + verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker( + dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID)); + verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker( + dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID)); + + dpc.stop(); + advanceTime(1); + + // two times, one for unfinished business and one for proximity + verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker( + dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID)); + verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker( + dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID)); + } + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + private Sensor setUpProxSensor() throws Exception { + Sensor proxSensor = TestUtils.createSensor( + Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY); + when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) + .thenReturn(List.of(proxSensor)); + return proxSensor; + } + + private SensorEventListener getSensorEventListener(Sensor sensor) { + verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), + eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); + return mSensorEventListenerCaptor.getValue(); + } + + private void setUpDisplay(int displayId, String uniqueId) { + DisplayInfo info = new DisplayInfo(); + DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); + + when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); + when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); + when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); + when(mLogicalDisplayMock.isEnabled()).thenReturn(true); + when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); + when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); + when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); + when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_PROXIMITY; + name = null; + } + }); + when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java new file mode 100644 index 000000000000..c2768d51970a --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.injector; + +/** Version of PackageResetHelper for testing. */ +public class FakePackageResetHelper extends PackageResetHelper { + + public FakePackageResetHelper() {} + + @Override + protected void onRegister() {} + + @Override + protected void onUnregister() {} + + public boolean isResetableForPackage(String packageName) { + return queryResetableForPackage(packageName); + } + + public void reset(String packageName) { + notifyPackageReset(packageName); + } +} + diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java index 02cacb7bc57c..ca730910943b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java @@ -35,6 +35,7 @@ public class TestInjector implements Injector { private final FakeDeviceIdleHelper mDeviceIdleHelper; private final FakeEmergencyHelper mEmergencyHelper; private final LocationUsageLogger mLocationUsageLogger; + private final FakePackageResetHelper mPackageResetHelper; public TestInjector(Context context) { mUserInfoHelper = new FakeUserInfoHelper(); @@ -50,6 +51,7 @@ public class TestInjector implements Injector { mDeviceIdleHelper = new FakeDeviceIdleHelper(); mEmergencyHelper = new FakeEmergencyHelper(); mLocationUsageLogger = new LocationUsageLogger(); + mPackageResetHelper = new FakePackageResetHelper(); } @Override @@ -116,4 +118,9 @@ public class TestInjector implements Injector { public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } + + @Override + public FakePackageResetHelper getPackageResetHelper() { + return mPackageResetHelper; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 0ac14432d113..20e4e8011327 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -1218,6 +1218,44 @@ public class LocationProviderManagerTest { assertThat(mProvider.getRequest().isActive()).isFalse(); } + @Test + public void testQueryPackageReset() { + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + + ILocationListener listener1 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + ILocationListener listener2 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener1); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener2); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + } + + @Test + public void testPackageReset() { + ILocationListener listener1 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1); + ILocationListener listener2 = createMockLocationListener(); + mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2); + + assertThat(mProvider.getRequest().isActive()).isTrue(); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue(); + + mInjector.getPackageResetHelper().reset("mypackage"); + assertThat(mProvider.getRequest().isActive()).isFalse(); + assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse(); + } + private ILocationListener createMockLocationListener() { return spy(new ILocationListener.Stub() { @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index bb5b1d8a67a9..cc57b9f913e3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -62,10 +62,10 @@ import com.android.server.compat.PlatformCompat import com.android.server.extendedtestutils.wheneverStatic import com.android.server.pm.dex.DexManager import com.android.server.pm.parsing.PackageParser2 -import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.permission.PermissionManagerServiceInternal +import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.parsing.ParsingPackage import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.resolution.ComponentResolver @@ -77,14 +77,6 @@ import com.android.server.testutils.mock import com.android.server.testutils.nullable import com.android.server.testutils.whenever import com.android.server.utils.WatchedArrayMap -import libcore.util.HexEncoding -import org.junit.Assert -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import org.mockito.AdditionalMatchers.or -import org.mockito.Mockito -import org.mockito.quality.Strictness import java.io.File import java.io.IOException import java.nio.file.Files @@ -93,6 +85,14 @@ import java.security.cert.CertificateException import java.util.Arrays import java.util.Random import java.util.concurrent.FutureTask +import libcore.util.HexEncoding +import org.junit.Assert +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.mockito.AdditionalMatchers.or +import org.mockito.Mockito +import org.mockito.quality.Strictness /** * A utility for mocking behavior of the system and dependencies when testing PackageManagerService @@ -522,7 +522,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.packageParser.parsePackage( or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage } whenever(mocks.packageParser.parsePackage( - or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage } + or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage } return parsedPackage } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt index 987192d41203..da929af3267b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt @@ -23,6 +23,7 @@ import android.os.Process import android.util.Log import com.android.server.pm.pkg.AndroidPackage import com.android.server.testutils.whenever +import java.io.File import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.notNullValue @@ -33,13 +34,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.argThat import org.mockito.Mockito import org.mockito.Mockito.verify -import java.io.File @RunWith(JUnit4::class) class PackageManagerServiceBootTest { @@ -120,8 +119,7 @@ class PackageManagerServiceBootTest { whenever(rule.mocks().packageParser.parsePackage( argThat { path: File -> path.path.contains("a.data.package") }, anyInt(), - anyBoolean(), - any())) + anyBoolean())) .thenThrow(PackageManagerException( PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!")) val pm = createPackageManagerService() diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java new file mode 100644 index 000000000000..33870f1d3b86 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.trust; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.service.trust.TrustAgentService; +import android.testing.TestableContext; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.widget.LockPatternUtils; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.SystemServiceManager; + +import com.google.android.collect.Lists; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Collections; + +public class TrustManagerServiceTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final MockContext mMockContext = new MockContext( + ApplicationProvider.getApplicationContext()); + + private static final String URI_SCHEME_PACKAGE = "package"; + private static final int TEST_USER_ID = UserHandle.USER_SYSTEM; + + private final TestLooper mLooper = new TestLooper(); + private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>(); + private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext); + private final TrustManagerService mService = new TrustManagerService(mMockContext); + + @Mock + private PackageManager mPackageManagerMock; + + @Before + public void setUp() { + resetTrustAgentLockSettings(); + LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class)); + + ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() { + @Override + public boolean matches(Intent argument) { + return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction()); + } + }; + when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher), + anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList); + when(mPackageManagerMock.checkPermission(any(), any())).thenReturn( + PackageManager.PERMISSION_GRANTED); + mMockContext.setMockPackageManager(mPackageManagerMock); + } + + @After + public void tearDown() { + resetTrustAgentLockSettings(); + LocalServices.removeServiceForTest(SystemServiceManager.class); + } + + @Test + public void firstBootCompleted_systemTrustAgentsEnabled() { + ComponentName systemTrustAgent1 = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + ComponentName systemTrustAgent2 = ComponentName.unflattenFromString( + "com.android/.AnotherSystemTrustAgent"); + ComponentName userTrustAgent1 = ComponentName.unflattenFromString( + "com.user/.UserTrustAgent"); + ComponentName userTrustAgent2 = ComponentName.unflattenFromString( + "com.user/.AnotherUserTrustAgent"); + addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true); + addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true); + addTrustAgent(userTrustAgent1, /* isSystemApp= */ false); + addTrustAgent(userTrustAgent2, /* isSystemApp= */ false); + + bootService(); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + systemTrustAgent1, systemTrustAgent2); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2); + } + + @Test + public void firstBootCompleted_defaultTrustAgentEnabled() { + ComponentName systemTrustAgent = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + ComponentName defaultTrustAgent = ComponentName.unflattenFromString( + "com.user/.DefaultTrustAgent"); + addTrustAgent(systemTrustAgent, /* isSystemApp= */ true); + addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false); + mMockContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.string.config_defaultTrustAgent, + defaultTrustAgent.flattenToString()); + + bootService(); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + defaultTrustAgent); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + systemTrustAgent, defaultTrustAgent); + } + + @Test + public void serviceBooted_knownAgentsNotSet_enabledAgentsNotUpdated() { + ComponentName trustAgent1 = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + ComponentName trustAgent2 = ComponentName.unflattenFromString( + "com.android/.AnotherSystemTrustAgent"); + initializeEnabledAgents(trustAgent1); + addTrustAgent(trustAgent1, /* isSystemApp= */ true); + addTrustAgent(trustAgent2, /* isSystemApp= */ true); + + bootService(); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + trustAgent1); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + trustAgent1, trustAgent2); + } + + @Test + public void serviceBooted_knownAgentsSet_enabledAgentsUpdated() { + ComponentName trustAgent1 = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + ComponentName trustAgent2 = ComponentName.unflattenFromString( + "com.android/.AnotherSystemTrustAgent"); + initializeEnabledAgents(trustAgent1); + initializeKnownAgents(trustAgent1); + addTrustAgent(trustAgent1, /* isSystemApp= */ true); + addTrustAgent(trustAgent2, /* isSystemApp= */ true); + + bootService(); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + trustAgent1, trustAgent2); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + trustAgent1, trustAgent2); + } + + @Test + public void newSystemTrustAgent_setToEnabledAndKnown() { + bootService(); + ComponentName newAgentComponentName = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + addTrustAgent(newAgentComponentName, /* isSystemApp= */ true); + + mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + newAgentComponentName); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + newAgentComponentName); + } + + @Test + public void newSystemTrustAgent_notEnabledWhenDefaultAgentIsSet() { + ComponentName defaultTrustAgent = ComponentName.unflattenFromString( + "com.user/.DefaultTrustAgent"); + addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false); + mMockContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.string.config_defaultTrustAgent, + defaultTrustAgent.flattenToString()); + bootService(); + ComponentName newAgentComponentName = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + addTrustAgent(newAgentComponentName, /* isSystemApp= */ true); + + mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + defaultTrustAgent); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + defaultTrustAgent, newAgentComponentName); + } + + @Test + public void newNonSystemTrustAgent_notEnabledButMarkedAsKnown() { + bootService(); + ComponentName newAgentComponentName = ComponentName.unflattenFromString( + "com.user/.UserTrustAgent"); + addTrustAgent(newAgentComponentName, /* isSystemApp= */ false); + + mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty(); + assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly( + newAgentComponentName); + } + + @Test + public void existingTrustAgentChanged_notEnabled() { + ComponentName systemTrustAgent1 = ComponentName.unflattenFromString( + "com.android/.SystemTrustAgent"); + ComponentName systemTrustAgent2 = ComponentName.unflattenFromString( + "com.android/.AnotherSystemTrustAgent"); + addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true); + addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true); + bootService(); + // Simulate user turning off systemTrustAgent2 + mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1), + TEST_USER_ID); + + mMockContext.sendPackageChangedBroadcast(systemTrustAgent2); + + assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly( + systemTrustAgent1); + } + + private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) { + ApplicationInfo applicationInfo = new ApplicationInfo(); + if (isSystemApp) { + applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + } + + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = agentComponentName.getPackageName(); + serviceInfo.name = agentComponentName.getClassName(); + serviceInfo.applicationInfo = applicationInfo; + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + mTrustAgentResolveInfoList.add(resolveInfo); + } + + private void initializeEnabledAgents(ComponentName... enabledAgents) { + mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID); + Settings.Secure.putIntForUser(mMockContext.getContentResolver(), + Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID); + } + + private void initializeKnownAgents(ComponentName... knownAgents) { + mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID); + Settings.Secure.putIntForUser(mMockContext.getContentResolver(), + Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID); + } + + private void bootService() { + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + } + + private void resetTrustAgentLockSettings() { + mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID); + mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID); + } + + /** A mock Context that allows the test process to send protected broadcasts. */ + private static final class MockContext extends TestableContext { + + private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers = + new ArrayList<>(); + + MockContext(Context base) { + super(base); + } + + @Override + @Nullable + public Intent registerReceiverAsUser(BroadcastReceiver receiver, + UserHandle user, IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler) { + + if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) { + mPackageChangedBroadcastReceivers.add(receiver); + } + return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, + scheduler); + } + + void sendPackageChangedBroadcast(ComponentName changedComponent) { + Intent intent = new Intent( + Intent.ACTION_PACKAGE_CHANGED, + Uri.fromParts(URI_SCHEME_PACKAGE, + changedComponent.getPackageName(), /* fragment= */ null)) + .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, + new String[]{changedComponent.getClassName()}) + .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID) + .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234)); + for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) { + receiver.onReceive(this, intent); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java index 693bc7dc4a42..574aaf00e460 100644 --- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java @@ -24,10 +24,12 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; @@ -99,6 +101,9 @@ public class CoreSettingsObserverTest { // To prevent NullPointerException at the constructor of ActivityManagerConstants. when(mResources.getStringArray(anyInt())).thenReturn(new String[0]); when(mResources.getIntArray(anyInt())).thenReturn(new int[0]); + final TypedArray mockTypedArray = mock(TypedArray.class); + when(mockTypedArray.length()).thenReturn(1); + when(mResources.obtainTypedArray(anyInt())).thenReturn(mockTypedArray); mAms = new ActivityManagerService(new TestInjector(mContext), mServiceThreadRule.getThread()); diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 3f4148bb49e1..a45144e23c17 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -588,6 +588,157 @@ public final class DeviceStateManagerServiceTest { }); } + @Test + public void requestBaseStateOverride() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + flushHandler(); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestBaseStateOverride(token, + OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + assertNotNull(callback.getLastNotifiedInfo()); + assertEquals(callback.getLastNotifiedInfo().baseState, + OTHER_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + OTHER_DEVICE_STATE.getIdentifier()); + + mService.getBinderService().cancelBaseStateOverride(); + flushHandler(); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set back to the requested state once the override is cleared. + assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertFalse(mService.getOverrideBaseState().isPresent()); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + + assertEquals(callback.getLastNotifiedInfo().baseState, + DEFAULT_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + DEFAULT_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException { + final DeviceState testDeviceState = new DeviceState(2, "TEST", 0); + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + mProvider.notifySupportedDeviceStates( + new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState }); + flushHandler(); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestBaseStateOverride(token, + OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + assertNotNull(callback.getLastNotifiedInfo()); + assertEquals(callback.getLastNotifiedInfo().baseState, + OTHER_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + OTHER_DEVICE_STATE.getIdentifier()); + + mProvider.setState(testDeviceState.getIdentifier()); + flushHandler(); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set to the new base state once the override is cleared. + assertEquals(mService.getCommittedState(), Optional.of(testDeviceState)); + assertEquals(mSysPropSetter.getValue(), + testDeviceState.getIdentifier() + ":" + testDeviceState.getName()); + assertEquals(mService.getBaseState(), Optional.of(testDeviceState)); + assertFalse(mService.getOverrideBaseState().isPresent()); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + testDeviceState.getIdentifier()); + + assertEquals(callback.getLastNotifiedInfo().baseState, + testDeviceState.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + testDeviceState.getIdentifier()); + } + + @Test + public void requestBaseState_unsupportedState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, + UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */); + }); + } + + @Test + public void requestBaseState_invalidState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE, + 0 /* flags */); + }); + } + + @Test + public void requestBaseState_beforeRegisteringCallback() { + assertThrows(IllegalStateException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, + DEFAULT_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + }); + } + private static void assertArrayEquals(int[] expected, int[] actual) { Assert.assertTrue(Arrays.equals(expected, actual)); } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index 2297c91818c0..430504ca2428 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.devicestate; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; @@ -57,7 +59,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addRequest(request); @@ -67,14 +69,14 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelExistingRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addRequest(secondRequest); @@ -85,7 +87,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelActiveRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); @@ -97,30 +99,90 @@ public final class OverrideRequestControllerTest { } @Test + public void addBaseStateRequest() { + OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(request)); + + mController.addBaseStateRequest(request); + assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE); + } + + @Test + public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(firstRequest)); + + mController.addBaseStateRequest(firstRequest); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(secondRequest)); + + mController.addBaseStateRequest(secondRequest); + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test + public void addBaseStateRequest_cancelActiveBaseStateRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + + mController.addBaseStateRequest(firstRequest); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + mController.cancelBaseStateOverrideRequest(); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test public void handleBaseStateChanged() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, - DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */); + DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - mController.handleBaseStateChanged(); + mController.addBaseStateRequest(baseStateRequest); + + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + + mController.handleBaseStateChanged(1); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test public void handleProcessDied() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + mController.handleProcessDied(0); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test @@ -128,13 +190,20 @@ public final class OverrideRequestControllerTest { mController.setStickyRequestsAllowed(true); OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + mController.handleProcessDied(0); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); mController.cancelStickyRequest(); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); @@ -143,22 +212,31 @@ public final class OverrideRequestControllerTest { @Test public void handleNewSupportedStates() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - mController.handleNewSupportedStates(new int[]{ 0, 1 }); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + + mController.handleNewSupportedStates(new int[]{0, 1}); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); - mController.handleNewSupportedStates(new int[]{ 0 }); + mController.handleNewSupportedStates(new int[]{0}); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test public void cancelOverrideRequestsTest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index f352de4ea54e..89ff2c258c26 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -48,7 +48,7 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class BrightnessMappingStrategyTest { - private static final int[] LUX_LEVELS = { + private static final float[] LUX_LEVELS = { 0, 5, 20, @@ -126,7 +126,8 @@ public class BrightnessMappingStrategyTest { private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final float MAXIMUM_GAMMA = 3.0f; - private static final int[] GAMMA_CORRECTION_LUX = { + + private static final float[] GAMMA_CORRECTION_LUX = { 0, 100, 1000, @@ -155,7 +156,7 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyMappingAtControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); @@ -170,7 +171,7 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyMappingBetweenControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); @@ -179,13 +180,13 @@ public class BrightnessMappingStrategyTest { final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON; assertTrue("Desired brightness should be between adjacent control points.", backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1] - && backlight < DISPLAY_LEVELS_BACKLIGHT[i]); + && backlight < DISPLAY_LEVELS_BACKLIGHT[i]); } } @Test public void testSimpleStrategyIgnoresNewConfiguration() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); @@ -200,7 +201,7 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNullConfiguration() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); DisplayDeviceConfig ddc = createDdc(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); @@ -214,8 +215,10 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingAtControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, + LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", physical); for (int i = 0; i < LUX_LEVELS.length; i++) { @@ -231,8 +234,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE, + LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", physical); Spline brightnessToNits = @@ -248,11 +252,12 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyUsesNewConfigurations() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); - final float[] lux = { 0f, 1f }; + final float[] lux = {0f, 1f}; final float[] nits = { DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1] @@ -273,8 +278,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyRecalculateSplines() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS); + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) { @@ -304,28 +310,30 @@ public class BrightnessMappingStrategyTest { @Test public void testDefaultStrategyIsPhysical() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, - DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); } @Test public void testNonStrictlyIncreasingLuxLevelsFails() { - final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length); + final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length); final int idx = lux.length / 2; - int tmp = lux[idx]; - lux[idx] = lux[idx+1]; - lux[idx+1] = tmp; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(); + float tmp = lux[idx]; + lux[idx] = lux[idx + 1]; + lux[idx + 1] = tmp; + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. - lux[idx] = lux[idx+1]; - res = createResources(lux, DISPLAY_LEVELS_NITS); + lux[idx] = lux[idx + 1]; + ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, + DISPLAY_LEVELS_NITS); strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); } @@ -333,62 +341,64 @@ public class BrightnessMappingStrategyTest { @Test public void testDifferentNumberOfControlPointValuesFails() { //Extra lux level - final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1); + final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1); // Make sure it's strictly increasing so that the only failure is the differing array // lengths lux[lux.length - 1] = lux[lux.length - 2] + 1; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); - res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT); + res = createResources(DISPLAY_LEVELS_BACKLIGHT); strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); // Extra backlight level final int[] backlight = Arrays.copyOf( - DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1); + DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; - res = createResources(LUX_LEVELS, backlight); + res = createResources(backlight); + ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY); strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); // Extra nits level - final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1); + final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1); nits[nits.length - 1] = nits[nits.length - 2] + 1; - res = createResources(LUX_LEVELS, nits); + res = createResources(EMPTY_INT_ARRAY); + ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits); strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(strategy); } @Test public void testPhysicalStrategyRequiresNitsMapping() { - Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS); + Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(physical); - res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS); + res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(physical); - res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS); + res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); assertNull(physical); } @Test public void testStrategiesAdaptToUserDataPoint() { - Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE, + LUX_LEVELS, DISPLAY_LEVELS_NITS); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc)); ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); - res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + res = createResources(DISPLAY_LEVELS_BACKLIGHT); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc)); } @@ -468,42 +478,19 @@ public class BrightnessMappingStrategyTest { assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/); } - private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) { - return createResources(luxLevels, brightnessLevelsBacklight, - EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/); - } - - private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits) { - return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - brightnessLevelsNits); + private Resources createResources(int[] brightnessLevelsBacklight) { + return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); } - private Resources createResourcesIdle(int[] luxLevels, float[] brightnessLevelsNits) { - return createResources(EMPTY_INT_ARRAY, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY, - luxLevels, brightnessLevelsNits); + private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { + return createResources(EMPTY_INT_ARRAY, + luxLevelsIdle, brightnessLevelsNitsIdle); } - private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, - float[] brightnessLevelsNits) { - return createResources(luxLevels, brightnessLevelsBacklight, brightnessLevelsNits, - EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); - - } - - private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, - float[] brightnessLevelsNits, int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { + private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle, + float[] brightnessLevelsNitsIdle) { Resources mockResources = mock(Resources.class); - - // For historical reasons, the lux levels resource implicitly defines the first point as 0, - // so we need to chop it off of the array the mock resource object returns. - // Don't mock if these values are not set. If we try to use them, we will fail. - if (luxLevels.length > 0) { - int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length); - when(mockResources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLevels)) - .thenReturn(luxLevelsResource); - } if (luxLevelsIdle.length > 0) { int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1, luxLevelsIdle.length); @@ -516,10 +503,6 @@ public class BrightnessMappingStrategyTest { com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) .thenReturn(brightnessLevelsBacklight); - TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits); - when(mockResources.obtainTypedArray( - com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) - .thenReturn(mockBrightnessLevelNits); TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle); when(mockResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)) @@ -549,6 +532,18 @@ public class BrightnessMappingStrategyTest { DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); when(mockDdc.getNits()).thenReturn(nitsArray); when(mockDdc.getBrightness()).thenReturn(backlightArray); + when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS); + when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY); + return mockDdc; + } + + private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray, + float[] luxLevelsFloat, float[] brightnessLevelsNits) { + DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); + when(mockDdc.getNits()).thenReturn(nitsArray); + when(mockDdc.getBrightness()).thenReturn(backlightArray); + when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat); + when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits); return mockDdc; } @@ -590,8 +585,10 @@ public class BrightnessMappingStrategyTest { final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources resources = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, + GAMMA_CORRECTION_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, mMockDwbc); // Let's start with a validity check: @@ -619,8 +616,10 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources resources = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, + GAMMA_CORRECTION_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, mMockDwbc); // Validity check: @@ -645,8 +644,10 @@ public class BrightnessMappingStrategyTest { public void testGammaCorrectionExtremeChangeAtCenter() { // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we // just make sure the adjustment reflects the change. - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources resources = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, + GAMMA_CORRECTION_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, mMockDwbc); assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); @@ -667,8 +668,10 @@ public class BrightnessMappingStrategyTest { final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); - DisplayDeviceConfig ddc = createDdc(); + Resources resources = createResources(EMPTY_INT_ARRAY); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, + GAMMA_CORRECTION_NITS); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, mMockDwbc); // Validity, as per tradition: diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt index d118661b84f5..40757182890c 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -233,16 +233,20 @@ class BatteryControllerTests { val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java) batteryController.registerBatteryListener(DEVICE_ID, listener, PID) verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) - verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), - eq(STATUS_CHARGING), eq(0.78f), anyLong()) + verify(listener).onBatteryStateChanged( + eq(DEVICE_ID), eq(true /*isPresent*/), + eq(STATUS_CHARGING), eq(0.78f), anyLong() + ) // If the battery presence for the InputDevice changes, the listener is notified. `when`(iInputManager.getInputDevice(DEVICE_ID)) .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false)) notifyDeviceChanged(DEVICE_ID) testLooper.dispatchNext() - verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(false /*isPresent*/), - eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong()) + verify(listener).onBatteryStateChanged( + eq(DEVICE_ID), eq(false /*isPresent*/), + eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong() + ) // Since the battery is no longer present, the UEventListener should be removed. verify(uEventManager).removeListener(uEventListener.value) @@ -251,10 +255,67 @@ class BatteryControllerTests { .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true)) notifyDeviceChanged(DEVICE_ID) testLooper.dispatchNext() - verify(listener, times(2)).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), - eq(STATUS_CHARGING), eq(0.78f), anyLong()) + verify(listener, times(2)).onBatteryStateChanged( + eq(DEVICE_ID), eq(true /*isPresent*/), + eq(STATUS_CHARGING), eq(0.78f), anyLong() + ) // Ensure that a new UEventListener was added. verify(uEventManager, times(2)) .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) } + + fun testStartPollingWhenListenerIsRegistered() { + val listener = createMockListener() + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) + batteryController.registerBatteryListener(DEVICE_ID, listener, PID) + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(), + eq(0.78f), anyLong()) + + // Assume there is a change in the battery state. Ensure the listener is not notified + // while the polling period has not elapsed. + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) + testLooper.moveTimeForward(1) + testLooper.dispatchAll() + verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + anyInt(), eq(0.80f), anyLong()) + + // Move the time forward so that the polling period has elapsed. + // The listener should be notified. + testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS - 1) + testLooper.dispatchNext() + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(), + eq(0.80f), anyLong()) + } + + @Test + fun testNoPollingWhenTheDeviceIsNotInteractive() { + batteryController.onInteractiveChanged(false /*interactive*/) + + val listener = createMockListener() + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) + batteryController.registerBatteryListener(DEVICE_ID, listener, PID) + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(), + eq(0.78f), anyLong()) + + // The battery state changed, but we should not be polling for battery changes when the + // device is not interactive. + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) + testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS) + testLooper.dispatchAll() + verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + anyInt(), eq(0.80f), anyLong()) + + // The device is now interactive. Battery state polling begins immediately. + batteryController.onInteractiveChanged(true /*interactive*/) + testLooper.dispatchNext() + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + anyInt(), eq(0.80f), anyLong()) + + // Ensure that we continue to poll for battery changes. + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90) + testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS) + testLooper.dispatchNext() + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + anyInt(), eq(0.90f), anyLong()) + } } diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java index 6a27f391ac5b..2cd5314e961f 100644 --- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java @@ -77,7 +77,7 @@ public class LogcatManagerServiceTest { private ILogd mLogdMock; private LogcatManagerService mService; - private LogcatManagerService.LogcatManagerServiceInternal mLocalService; + private LogcatManagerService.LogAccessDialogCallback mDialogCallback; private ContextWrapper mContextSpy; private OffsettableClock mClock; private TestLooper mTestLooper; @@ -118,7 +118,7 @@ public class LogcatManagerServiceTest { return mLogdMock; } }); - mLocalService = mService.getLocalService(); + mDialogCallback = mService.getDialogCallback(); mService.onStart(); } @@ -207,7 +207,7 @@ public class LogcatManagerServiceTest { mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); - mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1); @@ -222,7 +222,7 @@ public class LogcatManagerServiceTest { mTestLooper.dispatchAll(); verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM)); - mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1); @@ -240,7 +240,7 @@ public class LogcatManagerServiceTest { verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); - mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1); @@ -260,7 +260,7 @@ public class LogcatManagerServiceTest { verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt()); - mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1); @@ -275,7 +275,7 @@ public class LogcatManagerServiceTest { ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); - mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); @@ -293,7 +293,7 @@ public class LogcatManagerServiceTest { ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); - mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2); @@ -313,7 +313,7 @@ public class LogcatManagerServiceTest { ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); - mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2); @@ -330,7 +330,7 @@ public class LogcatManagerServiceTest { ActivityManager.PROCESS_STATE_TOP); mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); mTestLooper.dispatchAll(); - mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); + mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME); mTestLooper.dispatchAll(); advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index e04edc6d7db2..96707fde8edb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -216,6 +216,25 @@ public class UserManagerServiceTest { assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); } + @Test + public void assertIsUserSwitcherEnabled() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setMaxSupportedUsers(8); + assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isTrue(); + + setUserSwitch(false); + assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse(); + + setUserSwitch(true); + assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isTrue(); + + mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, userId); + assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isFalse(); + + mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userId); + setMaxSupportedUsers(1); + assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse(); + } @Test public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception { diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 107bbe1c79e3..2918365b94c8 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -79,10 +79,7 @@ <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity" android:turnScreenOn="true" android:showWhenLocked="true" /> - <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" - android:theme="@style/WhiteBackgroundTheme" - android:turnScreenOn="true" - android:showWhenLocked="true"/> + <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" /> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index 2956c14155b9..ac3d0f0d3f28 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -55,6 +56,8 @@ import android.window.IDisplayAreaOrganizer; import androidx.test.filters.SmallTest; +import com.google.android.collect.Lists; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -90,7 +93,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { @Before public void setUp() { // Let the Display to be created with the DualDisplay policy. - final DisplayAreaPolicy.Provider policyProvider = new DualDisplayTestPolicyProvider(); + setupDisplay(new DualDisplayTestPolicyProvider(mWm)); + } + + /** Populates fields for the test display. */ + private void setupDisplay(@NonNull DisplayAreaPolicy.Provider policyProvider) { doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait). @@ -383,6 +390,36 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder() { + // Define the DualDisplayArea hierarchy without IME_PLACEHOLDER in DAGs. + setupDisplay(new DualDisplayTestPolicyProvider(new ArrayList<>(), new ArrayList<>())); + setupImeWindow(); + final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); + final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); + + // By default, the ime container is attached to DC as defined in DAPolicy. + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); + + // firstActivityWin should be the target + final WindowState firstActivityWin = + createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, + "firstActivityWin"); + spyOn(firstActivityWin); + doReturn(true).when(firstActivityWin).canBeImeTarget(); + WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); + + // There is no IME_PLACEHOLDER in the firstRoot, so the ImeContainer will not be reparented. + assertThat(imeTarget).isEqualTo(firstActivityWin); + verify(mFirstRoot).placeImeContainer(imeContainer); + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); + assertThat(imeContainer.getParent().asDisplayArea().mFeatureId) + .isEqualTo(FEATURE_IME_PLACEHOLDER); + assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); + assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull(); + assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull(); + } + + @Test public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -523,9 +560,37 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */ static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider { + @NonNull + private final List<DisplayAreaPolicyBuilder.Feature> mFirstRootFeatures = new ArrayList<>(); + @NonNull + private final List<DisplayAreaPolicyBuilder.Feature> mSecondRootFeatures = + new ArrayList<>(); + + DualDisplayTestPolicyProvider(@NonNull WindowManagerService wmService) { + // Add IME_PLACEHOLDER by default. + this(Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( + wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build()), + Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( + wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build())); + } + + DualDisplayTestPolicyProvider( + @NonNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures, + @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures) { + mFirstRootFeatures.addAll(firstRootFeatures); + mSecondRootFeatures.addAll(secondRootFeatures); + } + @Override - public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, - RootDisplayArea root, DisplayArea.Tokens imeContainer) { + public DisplayAreaPolicy instantiate(@NonNull WindowManagerService wmService, + @NonNull DisplayContent content, @NonNull RootDisplayArea root, + @NonNull DisplayArea.Tokens imeContainer) { // Root // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation // layer @@ -554,12 +619,10 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { firstTdaList.add(firstTaskDisplayArea); DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy = new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) - .setTaskDisplayAreas(firstTdaList) - .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( - wmService.mPolicy, - "ImePlaceholder", FEATURE_IME_PLACEHOLDER) - .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) - .build()); + .setTaskDisplayAreas(firstTdaList); + for (DisplayAreaPolicyBuilder.Feature feature : mFirstRootFeatures) { + firstHierarchy.addFeature(feature); + } // Second final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot", @@ -570,12 +633,10 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { secondTdaList.add(secondTaskDisplayArea); DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy = new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot) - .setTaskDisplayAreas(secondTdaList) - .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( - wmService.mPolicy, - "ImePlaceholder", FEATURE_IME_PLACEHOLDER) - .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) - .build()); + .setTaskDisplayAreas(secondTdaList); + for (DisplayAreaPolicyBuilder.Feature feature : mSecondRootFeatures) { + secondHierarchy.addFeature(feature); + } return new DisplayAreaPolicyBuilder() .setRootHierarchy(rootHierarchy) diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java index d48711375d5c..3090590938c0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java @@ -78,7 +78,7 @@ public class InputMethodDialogWindowContextTest extends WindowTestsBase { public void setUp() throws Exception { // Let the Display be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = - new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); + new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm); Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); mWindowContext = new InputMethodDialogWindowContext(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java index 8cd8e9bde1b7..0b58428e9bfb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java @@ -16,10 +16,6 @@ package com.android.server.wm; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowInsets.Type.displayCutout; -import static android.view.WindowInsets.Type.statusBars; - import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertNotNull; @@ -27,31 +23,20 @@ import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.Instrumentation; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorSpace; import android.graphics.GraphicBuffer; -import android.graphics.Insets; import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.DataSpace; -import android.hardware.HardwareBuffer; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; import android.platform.test.annotations.Presubmit; -import android.view.IWindowManager; import android.view.PointerIcon; import android.view.SurfaceControl; -import android.view.cts.surfacevalidator.BitmapPixelChecker; -import android.view.cts.surfacevalidator.PixelColor; -import android.view.cts.surfacevalidator.SaveBitmapHelper; +import android.view.WindowManager; import android.window.ScreenCapture; -import android.window.ScreenCapture.SyncScreenCaptureListener; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -60,7 +45,6 @@ import androidx.test.rule.ActivityTestRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -76,8 +60,6 @@ public class ScreenshotTests { private static final int BUFFER_HEIGHT = 100; private final Instrumentation mInstrumentation = getInstrumentation(); - @Rule - public TestName mTestName = new TestName(); @Rule public ActivityTestRule<ScreenshotActivity> mActivityRule = @@ -113,8 +95,8 @@ public class ScreenshotTests { buffer.unlockCanvasAndPost(canvas); t.show(secureSC) - .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer)) - .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB) + .setBuffer(secureSC, buffer) + .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB)) .apply(true); ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC) @@ -130,69 +112,15 @@ public class ScreenshotTests { Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); screenshot.recycle(); - BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED); - Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight()); - int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); - int sizeOfBitmap = bounds.width() * bounds.height(); + int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap, + new PixelColor(PixelColor.RED)); + long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight(); boolean success = numMatchingPixels == sizeOfBitmap; swBitmap.recycle(); assertTrue(success); } - @Test - public void testCaptureDisplay() throws RemoteException { - IWindowManager windowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService(Context.WINDOW_SERVICE)); - SurfaceControl sc = new SurfaceControl.Builder() - .setName("Layer") - .setCallsite("testCaptureDisplay") - .build(); - - SurfaceControl.Transaction t = mActivity.addChildSc(sc); - mInstrumentation.waitForIdleSync(); - - GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, - PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER - | GraphicBuffer.USAGE_SW_WRITE_RARELY); - - Canvas canvas = buffer.lockCanvas(); - canvas.drawColor(Color.RED); - buffer.unlockCanvasAndPost(canvas); - - Point point = mActivity.getPositionBelowStatusBar(); - t.show(sc) - .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer)) - .setDataSpace(sc, DataSpace.DATASPACE_SRGB) - .setPosition(sc, point.x, point.y) - .apply(true); - - SyncScreenCaptureListener listener = new SyncScreenCaptureListener(); - windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener()); - ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot(); - assertNotNull(hardwareBuffer); - - Bitmap screenshot = hardwareBuffer.asBitmap(); - assertNotNull(screenshot); - - Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); - screenshot.recycle(); - - BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED); - Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y); - int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); - int pixelMatchSize = bounds.width() * bounds.height(); - boolean success = numMatchingPixels == pixelMatchSize; - - if (!success) { - SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage"); - } - swBitmap.recycle(); - assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize, - success); - } - public static class ScreenshotActivity extends Activity { private static final long WAIT_TIMEOUT_S = 5; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -202,6 +130,7 @@ public class ScreenshotTests { super.onCreate(savedInstanceState); getWindow().getDecorView().setPointerIcon( PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) { @@ -219,14 +148,88 @@ public class ScreenshotTests { } return t; } + } - public Point getPositionBelowStatusBar() { - Insets statusBarInsets = getWindow() - .getDecorView() - .getRootWindowInsets() - .getInsets(statusBars() | displayCutout()); + public abstract static class PixelChecker { + static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) { + int numMatchingPixels = 0; + for (int x = 0; x < bitmap.getWidth(); x++) { + for (int y = 0; y < bitmap.getHeight(); y++) { + int color = bitmap.getPixel(x, y); + if (matchesColor(pixelColor, color)) { + numMatchingPixels++; + } + } + } + return numMatchingPixels; + } + + static boolean matchesColor(PixelColor expectedColor, int color) { + final float red = Color.red(color); + final float green = Color.green(color); + final float blue = Color.blue(color); + final float alpha = Color.alpha(color); + + return alpha <= expectedColor.mMaxAlpha + && alpha >= expectedColor.mMinAlpha + && red <= expectedColor.mMaxRed + && red >= expectedColor.mMinRed + && green <= expectedColor.mMaxGreen + && green >= expectedColor.mMinGreen + && blue <= expectedColor.mMaxBlue + && blue >= expectedColor.mMinBlue; + } + } + + public static class PixelColor { + public static final int BLACK = 0xFF000000; + public static final int RED = 0xFF0000FF; + public static final int GREEN = 0xFF00FF00; + public static final int BLUE = 0xFFFF0000; + public static final int YELLOW = 0xFF00FFFF; + public static final int MAGENTA = 0xFFFF00FF; + public static final int WHITE = 0xFFFFFFFF; + + public static final int TRANSPARENT_RED = 0x7F0000FF; + public static final int TRANSPARENT_BLUE = 0x7FFF0000; + public static final int TRANSPARENT = 0x00000000; + + // Default to black + public short mMinAlpha; + public short mMaxAlpha; + public short mMinRed; + public short mMaxRed; + public short mMinBlue; + public short mMaxBlue; + public short mMinGreen; + public short mMaxGreen; + + public PixelColor(int color) { + short alpha = (short) ((color >> 24) & 0xFF); + short blue = (short) ((color >> 16) & 0xFF); + short green = (short) ((color >> 8) & 0xFF); + short red = (short) (color & 0xFF); + + mMinAlpha = (short) getMinValue(alpha); + mMaxAlpha = (short) getMaxValue(alpha); + mMinRed = (short) getMinValue(red); + mMaxRed = (short) getMaxValue(red); + mMinBlue = (short) getMinValue(blue); + mMaxBlue = (short) getMaxValue(blue); + mMinGreen = (short) getMinValue(green); + mMaxGreen = (short) getMaxValue(green); + } + + public PixelColor() { + this(BLACK); + } + + private int getMinValue(short color) { + return Math.max(color - 4, 0); + } - return new Point(statusBarInsets.left, statusBarInsets.top); + private int getMaxValue(short color) { + return Math.min(color + 4, 0xFF); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index f5fc5c13eb4c..70e6f2947c36 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -39,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import android.app.ActivityManagerInternal; @@ -81,6 +82,7 @@ import com.android.server.am.ActivityManagerService; import com.android.server.display.color.ColorDisplayService; import com.android.server.firewall.IntentFirewall; import com.android.server.input.InputManagerService; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; @@ -93,6 +95,7 @@ import org.junit.runners.model.Statement; import org.mockito.MockSettings; import org.mockito.Mockito; import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -283,6 +286,16 @@ public class SystemServicesTestRule implements TestRule { // StatusBarManagerInternal final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class); doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class))); + + // UserManagerInternal + final UserManagerInternal umi = mock(UserManagerInternal.class); + doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class)); + Answer<Boolean> isUserVisibleAnswer = invocation -> { + int userId = invocation.getArgument(0); + return userId == mWmService.mCurrentUserId; + }; + when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer); + when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer); } private void setUpActivityTaskManagerService() { @@ -403,6 +416,7 @@ public class SystemServicesTestRule implements TestRule { LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.removeServiceForTest(UserManagerInternal.class); } Description getDescription() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 9bdf750767b3..1404de253476 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; import static android.window.TaskFragmentOrganizer.getTransitionType; @@ -76,6 +77,7 @@ import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -271,7 +273,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentParentInfoChanged() { setupMockParent(mTaskFragment, mTask); - mTask.getConfiguration().smallestScreenWidthDp = 10; + mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 10; mController.onTaskFragmentAppeared( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); @@ -295,7 +297,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Trigger callback if the size is changed. clearInvocations(mOrganizer); - mTask.getConfiguration().smallestScreenWidthDp = 100; + mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 100; mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -304,7 +306,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Trigger callback if the windowing mode is changed. clearInvocations(mOrganizer); - mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + mTask.getTaskFragmentParentInfo().getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_PINNED); mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -727,6 +730,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_finishActivity() { + final ActivityRecord activity = createActivityRecord(mDisplayContent); + + mTransaction.finishActivity(activity.token); + assertApplyTransactionAllowed(mTransaction); + + assertTrue(activity.finishing); + } + + @Test public void testApplyTransaction_skipTransactionForUnregisterOrganizer() { mController.unregisterOrganizer(mIOrganizer); final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent); @@ -1268,7 +1281,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragmentTransaction.Change change = changes.get(0); assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType()); assertEquals(task.mTaskId, change.getTaskId()); - assertEquals(task.getConfiguration(), change.getTaskConfiguration()); + assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo()); } /** Asserts that there will be a transaction for TaskFragment error. */ @@ -1316,8 +1329,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups the mock Task as the parent of the given TaskFragment. */ private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { doReturn(mockParent).when(taskFragment).getTask(); - final Configuration taskConfig = new Configuration(); - doReturn(taskConfig).when(mockParent).getConfiguration(); + doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true)) + .when(mockParent).getTaskFragmentParentInfo(); // Task needs to be visible mockParent.lastActiveTime = 100; diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index e2dff965ef96..3331839193f8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -34,6 +34,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -1077,6 +1078,39 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testIsBehindStartingWindowChange() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final ActivityRecord activity0 = createActivityRecord(task); + final ActivityRecord activity1 = createActivityRecord(task); + doReturn(true).when(activity1).hasStartingWindow(); + + // Start states. + changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */)); + // End states. + activity0.mVisibleRequested = false; + activity1.mVisibleRequested = true; + + participants.add(activity0); + participants.add(activity1); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting + // window should cover the whole Task. + assertEquals(2, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)); + assertTrue(info.getChanges().get(1).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)); + + } + + @Test public void testFlagInTaskWithEmbeddedActivity() { final Transition transition = createTestTransition(TRANSIT_OPEN); final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; @@ -1123,7 +1157,7 @@ public class TransitionTests extends WindowTestsBase { } @Test - public void testFlagFillsTask() { + public void testFlagFillsTask_embeddingNotFillingTask() { final Transition transition = createTestTransition(TRANSIT_OPEN); final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; final ArraySet<WindowContainer> participants = transition.mParticipants; @@ -1168,6 +1202,67 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testFlagFillsTask_openActivityFillingTask() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord activity = createActivityRecord(task); + // Start states: set bounds to make sure the start bounds is ignored if it is not visible. + activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500)); + activity.mVisibleRequested = false; + changes.put(activity, new Transition.ChangeInfo(activity)); + // End states: reset bounds to fill Task. + activity.getConfiguration().windowConfiguration.setBounds(taskBounds); + activity.mVisibleRequested = true; + + participants.add(activity); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // Opening activity that is filling Task after transition should have the flag. + assertEquals(1, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + } + + @Test + public void testFlagFillsTask_closeActivityFillingTask() { + final Transition transition = createTestTransition(TRANSIT_CLOSE); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord activity = createActivityRecord(task); + // Start states: fills Task without override. + activity.mVisibleRequested = true; + changes.put(activity, new Transition.ChangeInfo(activity)); + // End states: set bounds to make sure the start bounds is ignored if it is not visible. + activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500)); + activity.mVisibleRequested = false; + + participants.add(activity); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // Closing activity that is filling Task before transition should have the flag. + assertEquals(1, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + } + + @Test public void testIncludeEmbeddedActivityReparent() { final Transition transition = createTestTransition(TRANSIT_OPEN); final Task task = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index 168567332795..f6d0bf110047 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -213,7 +213,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { public void testImeSwitchDialogWindowTokenRemovedOnDualDisplayContent_ListenToImeContainer() { // Let the Display to be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = - new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); + new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm); doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Create a DisplayContent with dual RootDisplayArea DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent = diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 8b63904baf31..46b4b76dc12f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -41,7 +41,6 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_ import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -69,7 +68,6 @@ import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.window.ClientWindowFrames; -import android.window.ScreenCapture; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; @@ -425,45 +423,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse(); } - @Test - public void testCaptureDisplay() { - Rect displayBounds = new Rect(0, 0, 100, 200); - spyOn(mDisplayContent); - when(mDisplayContent.getBounds()).thenReturn(displayBounds); - - // Null captureArgs - ScreenCapture.LayerCaptureArgs resultingArgs = - mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */); - assertEquals(displayBounds, resultingArgs.mSourceCrop); - - // Non null captureArgs, didn't set rect - ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build(); - resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs); - assertEquals(displayBounds, resultingArgs.mSourceCrop); - - // Non null captureArgs, invalid rect - captureArgs = new ScreenCapture.CaptureArgs.Builder<>() - .setSourceCrop(new Rect(0, 0, -1, -1)) - .build(); - resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs); - assertEquals(displayBounds, resultingArgs.mSourceCrop); - - // Non null captureArgs, null rect - captureArgs = new ScreenCapture.CaptureArgs.Builder<>() - .setSourceCrop(null) - .build(); - resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs); - assertEquals(displayBounds, resultingArgs.mSourceCrop); - - // Non null captureArgs, valid rect - Rect validRect = new Rect(0, 0, 10, 50); - captureArgs = new ScreenCapture.CaptureArgs.Builder<>() - .setSourceCrop(validRect) - .build(); - resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs); - assertEquals(validRect, resultingArgs.mSourceCrop); - } - private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); when(remoteToken.toWindowContainerToken()).thenReturn(wct); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 9562c1a5bcf9..e90a376e90f8 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -514,6 +514,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private boolean mConfigured; private boolean mAudioAccessoryConnected; private boolean mAudioAccessorySupported; + private boolean mConnectedToDataDisabledPort; + private int mPowerBrickConnectionStatus; private UsbAccessory mCurrentAccessory; private int mUsbNotificationId; @@ -952,12 +954,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser && status.isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE) && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE); + + boolean usbDataDisabled = + status.getUsbDataStatus() != UsbPortStatus.DATA_STATUS_ENABLED; + mConnectedToDataDisabledPort = status.isConnected() && usbDataDisabled; + mPowerBrickConnectionStatus = status.getPowerBrickConnectionStatus(); } else { mHostConnected = false; mSourcePower = false; mSinkPower = false; mAudioAccessoryConnected = false; mSupportsAllCombinations = false; + mConnectedToDataDisabledPort = false; + mPowerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN; } if (mHostConnected) { @@ -1265,6 +1274,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } else if (mHostConnected && mSinkPower && (mUsbCharging || mUsbAccessoryConnected)) { titleRes = com.android.internal.R.string.usb_charging_notification_title; id = SystemMessage.NOTE_USB_CHARGING; + } else if (mSinkPower && mConnectedToDataDisabledPort + && mPowerBrickConnectionStatus != UsbPortStatus.POWER_BRICK_STATUS_CONNECTED) { + // Show charging notification when USB Data is disabled on the port, and not + // connected to a wall charger. + titleRes = com.android.internal.R.string.usb_charging_notification_title; + id = SystemMessage.NOTE_USB_CHARGING; } if (id != mUsbNotificationId || force) { // clear notification if title needs changing diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index a1310849d84f..921f6e20c3ad 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -22,6 +22,7 @@ import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; +import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; @@ -189,7 +190,7 @@ final class HotwordDetectionConnection { final int mUser; final Context mContext; - @Nullable final AttentionManagerInternal mAttentionManagerInternal; + @Nullable AttentionManagerInternal mAttentionManagerInternal = null; final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = this::setProximityMeters; @@ -244,9 +245,11 @@ final class HotwordDetectionConnection { mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed); mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked(); - mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); - if (mAttentionManagerInternal != null) { - mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); + if (ENABLE_PROXIMITY_RESULT) { + mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); + if (mAttentionManagerInternal != null) { + mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); + } } mLastRestartInstant = Instant.now(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 4ee066c5c211..91b868dd3803 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -95,6 +95,7 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; +import com.android.internal.util.LatencyTracker; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -191,6 +192,8 @@ public class VoiceInteractionManagerService extends SystemService { mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mServiceStub.systemRunning(isSafeMode()); + } else if (phase == PHASE_BOOT_COMPLETED) { + mServiceStub.registerVoiceInteractionSessionListener(mLatencyLoggingListener); } } @@ -2339,4 +2342,36 @@ public class VoiceInteractionManagerService extends SystemService { } }; } + + /** + * End the latency tracking log for keyphrase hotword invocation. + * The measurement covers from when the SoundTrigger HAL emits an event, captured in + * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging} + * to when the {@link android.service.voice.VoiceInteractionSession} system UI view is shown. + */ + private final IVoiceInteractionSessionListener mLatencyLoggingListener = + new IVoiceInteractionSessionListener.Stub() { + @Override + public void onVoiceSessionShown() throws RemoteException {} + + @Override + public void onVoiceSessionHidden() throws RemoteException {} + + @Override + public void onVoiceSessionWindowVisibilityChanged(boolean visible) + throws RemoteException { + if (visible) { + LatencyTracker.getInstance(mContext) + .onActionEnd(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION); + } + } + + @Override + public void onSetUiHints(Bundle args) throws RemoteException {} + + @Override + public IBinder asBinder() { + return mServiceStub; + } + }; } diff --git a/telephony/OWNERS b/telephony/OWNERS index e0c5f8fa214e..025869dd2999 100644 --- a/telephony/OWNERS +++ b/telephony/OWNERS @@ -17,3 +17,6 @@ per-file SubscriptionManager.java=jackyu@google.com,amruthr@google.com per-file SubscriptionInfo.java=set noparent per-file SubscriptionInfo.java=jackyu@google.com,amruthr@google.com +# Requiring TL ownership for new carrier config keys. +per-file CarrierConfigManager.java=set noparent +per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index 58974eec838b..061b71b25275 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -105,17 +105,23 @@ public final class AnomalyReporter { * @param carrierId the carrier of the id associated with this event. */ public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) { - // Don't report if the server-side flag isn't loaded, as it implies other anomaly report - // related config hasn't loaded. - boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, false); - if (!isAnomalyReportEnabledFromServer) return; - if (sContext == null) { Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId); return; } + // Don't report if the server-side flag isn't loaded, as it implies other anomaly report + // related config hasn't loaded. + try { + boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, + false); + if (!isAnomalyReportEnabledFromServer) return; + } catch (Exception e) { + Rlog.w(TAG, "Unable to read device config, dropping event=" + eventId); + return; + } + TelephonyStatsLog.write( TELEPHONY_ANOMALY_DETECTED, carrierId, diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index b6944eb17b0c..2a1efed63dd2 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -52,7 +52,9 @@ import com.android.internal.telephony.ICarrierConfigLoader; import com.android.telephony.Rlog; import java.util.List; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Provides access to telephony configuration values that are carrier-specific. @@ -8647,6 +8649,73 @@ public class CarrierConfigManager { "unthrottle_data_retry_when_tac_changes_bool"; /** + * A list of premium capabilities the carrier supports. Applications can prompt users to + * purchase these premium capabilities from their carrier for a network boost. + * Valid values are any of {@link TelephonyManager.PremiumCapability}. + * + * This is empty by default, indicating that no premium capabilities are supported. + * + * @see TelephonyManager#isPremiumCapabilityAvailableForPurchase(int) + * @see TelephonyManager#purchasePremiumCapability(int, Executor, Consumer) + */ + public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY = + "supported_premium_capabilities_int_array"; + + /** + * The amount of time in milliseconds the notification for a network boost via + * premium capabilities will be visible to the user after + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} + * requests user action to purchase the boost from the carrier. Once the timeout expires, + * the booster notification will be automatically dismissed and the request will fail with + * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT}. + * + * The default value is 30 minutes. + */ + public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = + "premium_capability_notification_display_timeout_millis_long"; + + /** + * The amount of time in milliseconds that the notification for a network boost via + * premium capabilities should be blocked when + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} + * returns a failure due to user action or timeout. + * + * The default value is 30 minutes. + * + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT + */ + public static final String + KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = + "premium_capability_notification_backoff_hysteresis_time_millis_long"; + + /** + * The amount of time in milliseconds that the purchase request should be throttled when + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} + * returns a failure due to the carrier. + * + * The default value is 30 minutes. + * + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED + */ + public static final String + KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = + "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long"; + + /** + * The URL to redirect to when the user clicks on the notification for a network boost via + * premium capabilities after applications call + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}. + * If the URL is empty or invalid, the purchase request will return + * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED}. + * + * This is empty by default. + */ + public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING = + "premium_capability_purchase_url_string"; + + /** * IWLAN handover rules that determine whether handover is allowed or disallowed between * cellular and IWLAN. * @@ -9312,6 +9381,15 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false); sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false); + sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[]{}); + sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG, + TimeUnit.MINUTES.toMillis(30)); + sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG, + TimeUnit.MINUTES.toMillis(30)); + sDefaults.putLong( + KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG, + TimeUnit.MINUTES.toMillis(30)); + sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null); sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{ "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, " + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"}); diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index c75de42707a6..ac892da765e7 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -265,7 +265,7 @@ public abstract class NetworkService extends Service { /** @hide */ @Override public void onDestroy() { - mHandlerThread.quit(); + mHandlerThread.quitSafely(); super.onDestroy(); } diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java index 3c1824539d1f..7053b44df1a2 100644 --- a/telephony/java/android/telephony/SignalThresholdInfo.java +++ b/telephony/java/android/telephony/SignalThresholdInfo.java @@ -100,24 +100,34 @@ public final class SignalThresholdInfo implements Parcelable { */ public static final int SIGNAL_MEASUREMENT_TYPE_SSSINR = 8; + /** + * The ratio between the received energy from the pilot signal CPICH per chip (Ec) to the + * noise density (No). + * Range: -24 dBm to 1 dBm. + * Used RAN: {@link AccessNetworkConstants.AccessNetworkType#UTRAN} + * Reference: 3GPP TS 25.215 5.1.5 + */ + public static final int SIGNAL_MEASUREMENT_TYPE_ECNO = 9; + /** @hide */ - @IntDef(prefix = {"SIGNAL_MEASUREMENT_TYPE_"}, value = { - SIGNAL_MEASUREMENT_TYPE_UNKNOWN, - SIGNAL_MEASUREMENT_TYPE_RSSI, - SIGNAL_MEASUREMENT_TYPE_RSCP, - SIGNAL_MEASUREMENT_TYPE_RSRP, - SIGNAL_MEASUREMENT_TYPE_RSRQ, - SIGNAL_MEASUREMENT_TYPE_RSSNR, - SIGNAL_MEASUREMENT_TYPE_SSRSRP, - SIGNAL_MEASUREMENT_TYPE_SSRSRQ, - SIGNAL_MEASUREMENT_TYPE_SSSINR - }) + @IntDef( + prefix = {"SIGNAL_MEASUREMENT_TYPE_"}, + value = { + SIGNAL_MEASUREMENT_TYPE_UNKNOWN, + SIGNAL_MEASUREMENT_TYPE_RSSI, + SIGNAL_MEASUREMENT_TYPE_RSCP, + SIGNAL_MEASUREMENT_TYPE_RSRP, + SIGNAL_MEASUREMENT_TYPE_RSRQ, + SIGNAL_MEASUREMENT_TYPE_RSSNR, + SIGNAL_MEASUREMENT_TYPE_SSRSRP, + SIGNAL_MEASUREMENT_TYPE_SSRSRQ, + SIGNAL_MEASUREMENT_TYPE_SSSINR, + SIGNAL_MEASUREMENT_TYPE_ECNO + }) @Retention(RetentionPolicy.SOURCE) - public @interface SignalMeasurementType { - } + public @interface SignalMeasurementType {} - @SignalMeasurementType - private final int mSignalMeasurementType; + @SignalMeasurementType private final int mSignalMeasurementType; /** * A hysteresis time in milliseconds to prevent flapping. @@ -149,8 +159,7 @@ public final class SignalThresholdInfo implements Parcelable { /** * The radio access network type associated with the signal thresholds. */ - @AccessNetworkConstants.RadioAccessNetworkType - private final int mRan; + @AccessNetworkConstants.RadioAccessNetworkType private final int mRan; /** * Indicates the hysteresisMs is disabled. @@ -166,7 +175,6 @@ public final class SignalThresholdInfo implements Parcelable { */ public static final int HYSTERESIS_DB_DISABLED = 0; - /** * Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_RSSI}. * @@ -280,6 +288,20 @@ public final class SignalThresholdInfo implements Parcelable { public static final int SIGNAL_SSSINR_MAX_VALUE = 40; /** + * Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_ECNO}. + * + * @hide + */ + public static final int SIGNAL_ECNO_MIN_VALUE = -24; + + /** + * Maximum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_ECNO}. + * + * @hide + */ + public static final int SIGNAL_ECNO_MAX_VALUE = 1; + + /** * The minimum number of thresholds allowed in each SignalThresholdInfo. * * @hide @@ -303,9 +325,13 @@ public final class SignalThresholdInfo implements Parcelable { * @param thresholds threshold value * @param isEnabled isEnabled */ - private SignalThresholdInfo(@AccessNetworkConstants.RadioAccessNetworkType int ran, - @SignalMeasurementType int signalMeasurementType, int hysteresisMs, int hysteresisDb, - @NonNull int[] thresholds, boolean isEnabled) { + private SignalThresholdInfo( + @AccessNetworkConstants.RadioAccessNetworkType int ran, + @SignalMeasurementType int signalMeasurementType, + int hysteresisMs, + int hysteresisDb, + @NonNull int[] thresholds, + boolean isEnabled) { Objects.requireNonNull(thresholds, "thresholds must not be null"); validateRanWithMeasurementType(ran, signalMeasurementType); validateThresholdRange(signalMeasurementType, thresholds); @@ -399,6 +425,7 @@ public final class SignalThresholdInfo implements Parcelable { * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRP * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRQ * @see #SIGNAL_MEASUREMENT_TYPE_SSSINR + * @see #SIGNAL_MEASUREMENT_TYPE_ECNO * @see #getThresholds() for more details on signal strength thresholds */ public @NonNull Builder setThresholds(@NonNull int[] thresholds) { @@ -417,18 +444,20 @@ public final class SignalThresholdInfo implements Parcelable { */ public @NonNull Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) { Objects.requireNonNull(thresholds, "thresholds must not be null"); - if (!isSystem && (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED - || thresholds.length > MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED)) { + if (!isSystem + && (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED + || thresholds.length > MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED)) { throw new IllegalArgumentException( - "thresholds length must between " + MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED - + " and " + MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED); + "thresholds length must between " + + MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED + + " and " + + MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED); } mThresholds = thresholds.clone(); Arrays.sort(mThresholds); return this; } - /** * Set if the modem should trigger the report based on the criteria. * @@ -451,8 +480,13 @@ public final class SignalThresholdInfo implements Parcelable { * measurement type */ public @NonNull SignalThresholdInfo build() { - return new SignalThresholdInfo(mRan, mSignalMeasurementType, mHysteresisMs, - mHysteresisDb, mThresholds, mIsEnabled); + return new SignalThresholdInfo( + mRan, + mSignalMeasurementType, + mHysteresisMs, + mHysteresisDb, + mThresholds, + mIsEnabled); } } @@ -508,6 +542,7 @@ public final class SignalThresholdInfo implements Parcelable { * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRP * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRQ * @see #SIGNAL_MEASUREMENT_TYPE_SSSINR + * @see #SIGNAL_MEASUREMENT_TYPE_ECNO */ public @NonNull int[] getThresholds() { return mThresholds.clone(); @@ -574,8 +609,13 @@ public final class SignalThresholdInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mRan, mSignalMeasurementType, mHysteresisMs, mHysteresisDb, - Arrays.hashCode(mThresholds), mIsEnabled); + return Objects.hash( + mRan, + mSignalMeasurementType, + mHysteresisMs, + mHysteresisDb, + Arrays.hashCode(mThresholds), + mIsEnabled); } public static final @NonNull Parcelable.Creator<SignalThresholdInfo> CREATOR = @@ -594,13 +634,20 @@ public final class SignalThresholdInfo implements Parcelable { @Override public String toString() { return new StringBuilder("SignalThresholdInfo{") - .append("mRan=").append(mRan) - .append(" mSignalMeasurementType=").append(mSignalMeasurementType) - .append(" mHysteresisMs=").append(mHysteresisMs) - .append(" mHysteresisDb=").append(mHysteresisDb) - .append(" mThresholds=").append(Arrays.toString(mThresholds)) - .append(" mIsEnabled=").append(mIsEnabled) - .append("}").toString(); + .append("mRan=") + .append(mRan) + .append(" mSignalMeasurementType=") + .append(mSignalMeasurementType) + .append(" mHysteresisMs=") + .append(mHysteresisMs) + .append(" mHysteresisDb=") + .append(mHysteresisDb) + .append(" mThresholds=") + .append(Arrays.toString(mThresholds)) + .append(" mIsEnabled=") + .append(mIsEnabled) + .append("}") + .toString(); } /** @@ -624,6 +671,8 @@ public final class SignalThresholdInfo implements Parcelable { return threshold >= SIGNAL_SSRSRQ_MIN_VALUE && threshold <= SIGNAL_SSRSRQ_MAX_VALUE; case SIGNAL_MEASUREMENT_TYPE_SSSINR: return threshold >= SIGNAL_SSSINR_MIN_VALUE && threshold <= SIGNAL_SSSINR_MAX_VALUE; + case SIGNAL_MEASUREMENT_TYPE_ECNO: + return threshold >= SIGNAL_ECNO_MIN_VALUE && threshold <= SIGNAL_ECNO_MAX_VALUE; default: return false; } @@ -640,6 +689,7 @@ public final class SignalThresholdInfo implements Parcelable { return ran == AccessNetworkConstants.AccessNetworkType.GERAN || ran == AccessNetworkConstants.AccessNetworkType.CDMA2000; case SIGNAL_MEASUREMENT_TYPE_RSCP: + case SIGNAL_MEASUREMENT_TYPE_ECNO: return ran == AccessNetworkConstants.AccessNetworkType.UTRAN; case SIGNAL_MEASUREMENT_TYPE_RSRP: case SIGNAL_MEASUREMENT_TYPE_RSRQ: @@ -663,13 +713,15 @@ public final class SignalThresholdInfo implements Parcelable { } } - private void validateThresholdRange(@SignalMeasurementType int signalMeasurement, - int[] thresholds) { + private void validateThresholdRange( + @SignalMeasurementType int signalMeasurement, int[] thresholds) { for (int threshold : thresholds) { if (!isValidThreshold(signalMeasurement, threshold)) { throw new IllegalArgumentException( - "invalid signal measurement type: " + signalMeasurement - + " with threshold: " + threshold); + "invalid signal measurement type: " + + signalMeasurement + + " with threshold: " + + threshold); } } } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index cb985bf2cda5..eb96d3739119 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -18,6 +18,7 @@ package android.telephony; import static android.text.TextUtils.formatSimple; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -37,6 +38,9 @@ import android.os.Build; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; +import android.telephony.SubscriptionManager.ProfileClass; +import android.telephony.SubscriptionManager.SimDisplayNameSource; +import android.telephony.SubscriptionManager.SubscriptionType; import android.telephony.SubscriptionManager.UsageSetting; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -55,7 +59,6 @@ import java.util.Objects; * A Parcelable class for Subscription Information. */ public class SubscriptionInfo implements Parcelable { - /** * Size of text to render on the icon. */ @@ -65,162 +68,180 @@ public class SubscriptionInfo implements Parcelable { * Subscription Identifier, this is a device unique number * and not an index into an array */ - private int mId; + private final int mId; /** - * The GID for a SIM that maybe associated with this subscription, empty if unknown + * The ICCID of the SIM that is associated with this subscription, empty if unknown. */ - private String mIccId; + @NonNull + private final String mIccId; /** - * The index of the slot that currently contains the subscription - * and not necessarily unique and maybe INVALID_SLOT_ID if unknown + * The index of the SIM slot that currently contains the subscription and not necessarily unique + * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription + * is inactive. */ - private int mSimSlotIndex; + private final int mSimSlotIndex; /** - * The name displayed to the user that identifies this subscription + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. */ - private CharSequence mDisplayName; + @NonNull + private final CharSequence mDisplayName; /** - * String that identifies SPN/PLMN - * TODO : Add a new field that identifies only SPN for a sim + * The name displayed to the user that identifies subscription provider name. This name is the + * SPN displayed in status bar and many other places. Can't be renamed by the user. */ - private CharSequence mCarrierName; + @NonNull + private final CharSequence mCarrierName; /** * The subscription carrier id. + * * @see TelephonyManager#getSimCarrierId() */ - private int mCarrierId; + private final int mCarrierId; /** - * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN, - * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT. + * The source of the {@link #mCarrierName}. */ - private int mNameSource; + @SimDisplayNameSource + private final int mNameSource; /** - * The color to be used for tinting the icon when displaying to the user + * The color to be used for tinting the icon when displaying to the user. */ - private int mIconTint; + private final int mIconTint; /** - * A number presented to the user identify this subscription + * The number presented to the user identify this subscription. */ - private String mNumber; + @NonNull + private final String mNumber; /** - * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ - private int mDataRoaming; + private final int mDataRoaming; /** - * SIM icon bitmap cache + * SIM icon bitmap cache. */ - @Nullable private Bitmap mIconBitmap; + @Nullable + private Bitmap mIconBitmap; /** - * Mobile Country Code + * Mobile Country Code. */ - private String mMcc; + @Nullable + private final String mMcc; /** - * Mobile Network Code + * Mobile Network Code. */ - private String mMnc; + @Nullable + private final String mMnc; /** - * EHPLMNs associated with the subscription + * EHPLMNs associated with the subscription. */ - private String[] mEhplmns; + @NonNull + private final String[] mEhplmns; /** - * HPLMNs associated with the subscription + * HPLMNs associated with the subscription. */ - private String[] mHplmns; + @NonNull + private final String[] mHplmns; /** - * ISO Country code for the subscription's provider + * ISO Country code for the subscription's provider. */ - private String mCountryIso; + @NonNull + private final String mCountryIso; /** - * Whether the subscription is an embedded one. + * Whether the subscription is from eSIM. */ - private boolean mIsEmbedded; + private final boolean mIsEmbedded; /** - * The access rules for this subscription, if it is embedded and defines any. - * This does not include access rules for non-embedded subscriptions. + * The access rules for this subscription, if it is embedded and defines any. This does not + * include access rules for non-embedded subscriptions. */ @Nullable - private UiccAccessRule[] mNativeAccessRules; + private final UiccAccessRule[] mNativeAccessRules; /** * The carrier certificates for this subscription that are saved in carrier configs. * This does not include access rules from the Uicc, whether embedded or non-embedded. */ @Nullable - private UiccAccessRule[] mCarrierConfigAccessRules; + private final UiccAccessRule[] mCarrierConfigAccessRules; /** * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the * EID for an eUICC card. */ - private String mCardString; + @NonNull + private final String mCardString; /** - * The card ID of the SIM card. This maps uniquely to the card string. + * The card ID of the SIM card. This maps uniquely to {@link #mCardString}. */ - private int mCardId; + private final int mCardId; /** * Whether the subscription is opportunistic. */ - private boolean mIsOpportunistic; + private final boolean mIsOpportunistic; /** - * A UUID assigned to the subscription group. It returns null if not assigned. - * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details. + * A UUID assigned to the subscription group. {@code null} if not assigned. + * + * @see SubscriptionManager#createSubscriptionGroup(List) */ @Nullable - private ParcelUuid mGroupUUID; + private final ParcelUuid mGroupUuid; /** - * A package name that specifies who created the group. Null if mGroupUUID is null. + * A package name that specifies who created the group. Empty if not available. */ - private String mGroupOwner; + @NonNull + private final String mGroupOwner; /** - * Whether group of the subscription is disabled. - * This is only useful if it's a grouped opportunistic subscription. In this case, if all - * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM - * or deactivated eSIM profile), we should disable this opportunistic subscription. + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions + * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable + * this opportunistic subscription. */ - private boolean mIsGroupDisabled = false; + private final boolean mIsGroupDisabled; /** - * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL - * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET. - * A profile on the eUICC can be defined as test, operational, provisioning, or unset. - * The profile class will be populated from the profile metadata if present. Otherwise, - * the profile class defaults to unset if there is no profile metadata or the subscription - * is not on an eUICC ({@link #isEmbedded} returns false). + * The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). */ - private int mProfileClass; + @ProfileClass + private final int mProfileClass; /** - * Type of subscription + * Type of the subscription. */ - private int mSubscriptionType; + @SubscriptionType + private final int mType; /** * Whether uicc applications are configured to enable or disable. * By default it's true. */ - private boolean mAreUiccApplicationsEnabled = true; + private final boolean mAreUiccApplicationsEnabled; /** * The port index of the Uicc card. @@ -230,25 +251,16 @@ public class SubscriptionInfo implements Parcelable { /** * Subscription's preferred usage setting. */ - private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; - - /** - * Public copy constructor. - * @hide - */ - public SubscriptionInfo(SubscriptionInfo info) { - this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName, - info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap, - info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules, - info.mCardString, info.mCardId, info.mIsOpportunistic, - info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled, - info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner, - info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled); - } + @UsageSetting + private final int mUsageSetting; /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -262,7 +274,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -276,7 +292,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -293,7 +313,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -311,49 +335,94 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId, - boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, + boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled, int carrierId, int profileClass, int subType, @Nullable String groupOwner, @Nullable UiccAccessRule[] carrierConfigAccessRules, boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; - this.mDisplayName = displayName; + this.mDisplayName = displayName; this.mCarrierName = carrierName; this.mNameSource = nameSource; this.mIconTint = iconTint; this.mNumber = number; this.mDataRoaming = roaming; this.mIconBitmap = icon; - this.mMcc = mcc; - this.mMnc = mnc; - this.mCountryIso = countryIso; + this.mMcc = TextUtils.emptyIfNull(mcc); + this.mMnc = TextUtils.emptyIfNull(mnc); + this.mHplmns = null; + this.mEhplmns = null; + this.mCountryIso = TextUtils.emptyIfNull(countryIso); this.mIsEmbedded = isEmbedded; this.mNativeAccessRules = nativeAccessRules; - this.mCardString = cardString; + this.mCardString = TextUtils.emptyIfNull(cardString); this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; - this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID); + this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); this.mIsGroupDisabled = isGroupDisabled; this.mCarrierId = carrierId; this.mProfileClass = profileClass; - this.mSubscriptionType = subType; - this.mGroupOwner = groupOwner; + this.mType = subType; + this.mGroupOwner = TextUtils.emptyIfNull(groupOwner); this.mCarrierConfigAccessRules = carrierConfigAccessRules; this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled; this.mPortIndex = portIndex; this.mUsageSetting = usageSetting; } + /** - * @return the subscription ID. + * Constructor from builder. + * + * @param builder Builder of {@link SubscriptionInfo}. + */ + private SubscriptionInfo(@NonNull Builder builder) { + this.mId = builder.mId; + this.mIccId = builder.mIccId; + this.mSimSlotIndex = builder.mSimSlotIndex; + this.mDisplayName = builder.mDisplayName; + this.mCarrierName = builder.mCarrierName; + this.mNameSource = builder.mNameSource; + this.mIconTint = builder.mIconTint; + this.mNumber = builder.mNumber; + this.mDataRoaming = builder.mDataRoaming; + this.mIconBitmap = builder.mIconBitmap; + this.mMcc = builder.mMcc; + this.mMnc = builder.mMnc; + this.mEhplmns = builder.mEhplmns; + this.mHplmns = builder.mHplmns; + this.mCountryIso = builder.mCountryIso; + this.mIsEmbedded = builder.mIsEmbedded; + this.mNativeAccessRules = builder.mNativeAccessRules; + this.mCardString = builder.mCardString; + this.mCardId = builder.mCardId; + this.mIsOpportunistic = builder.mIsOpportunistic; + this.mGroupUuid = builder.mGroupUuid; + this.mIsGroupDisabled = builder.mIsGroupDisabled; + this.mCarrierId = builder.mCarrierId; + this.mProfileClass = builder.mProfileClass; + this.mType = builder.mType; + this.mGroupOwner = builder.mGroupOwner; + this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules; + this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled; + this.mPortIndex = builder.mPortIndex; + this.mUsageSetting = builder.mUsageSetting; + } + + /** + * @return The subscription ID. */ public int getSubscriptionId() { - return this.mId; + return mId; } /** @@ -370,78 +439,56 @@ public class SubscriptionInfo implements Parcelable { * @return the ICC ID, or an empty string if one of these requirements is not met */ public String getIccId() { - return this.mIccId; + return mIccId; } /** - * @hide - */ - public void clearIccId() { - this.mIccId = ""; - } - - /** - * @return the slot index of this Subscription's SIM card. + * @return The index of the SIM slot that currently contains the subscription and not + * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or + * the subscription is inactive. */ public int getSimSlotIndex() { - return this.mSimSlotIndex; + return mSimSlotIndex; } /** - * @return the carrier id of this Subscription carrier. + * @return The carrier id of this subscription carrier. + * * @see TelephonyManager#getSimCarrierId() */ public int getCarrierId() { - return this.mCarrierId; + return mCarrierId; } /** - * @return the name displayed to the user that identifies this subscription + * @return The name displayed to the user that identifies this subscription. This name is + * used in Settings page and can be renamed by the user. + * + * @see #getCarrierName() */ public CharSequence getDisplayName() { - return this.mDisplayName; + return mDisplayName; } /** - * Sets the name displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setDisplayName(CharSequence name) { - this.mDisplayName = name; - } - - /** - * @return the name displayed to the user that identifies Subscription provider name + * @return The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @see #getDisplayName() */ public CharSequence getCarrierName() { - return this.mCarrierName; - } - - /** - * Sets the name displayed to the user that identifies Subscription provider name - * @hide - */ - public void setCarrierName(CharSequence name) { - this.mCarrierName = name; + return mCarrierName; } /** - * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or - * NAME_SOURCE_USER_INPUT. + * @return The source of the {@link #getCarrierName()}. + * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SimDisplayNameSource public int getNameSource() { - return this.mNameSource; - } - - /** - * @hide - */ - public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) { - mEhplmns = ehplmns; - mHplmns = hplmns; + return mNameSource; } /** @@ -499,15 +546,6 @@ public class SubscriptionInfo implements Parcelable { } /** - * Sets the color displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setIconTint(int iconTint) { - this.mIconTint = iconTint; - } - - /** * Returns the number of this subscription. * * Starting with API level 30, returns the number of this subscription if the calling app meets @@ -533,28 +571,23 @@ public class SubscriptionInfo implements Parcelable { } /** - * @hide - */ - public void clearNumber() { - mNumber = ""; - } - - /** - * @return the data roaming state for this subscription, either - * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ public int getDataRoaming() { - return this.mDataRoaming; + return mDataRoaming; } /** - * @return the MCC. + * @return The mobile country code. + * * @deprecated Use {@link #getMccString()} instead. */ @Deprecated public int getMcc() { try { - return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc); + return mMcc == null ? 0 : Integer.parseInt(mMcc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number"); return 0; @@ -562,13 +595,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the MNC. + * @return The mobile network code. + * * @deprecated Use {@link #getMncString()} instead. */ @Deprecated public int getMnc() { try { - return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc); + return mMnc == null ? 0 : Integer.parseInt(mMnc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number"); return 0; @@ -576,36 +610,40 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return The MCC, as a string. + * @return The mobile country code. */ - public @Nullable String getMccString() { - return this.mMcc; + @Nullable + public String getMccString() { + return mMcc; } /** - * @return The MNC, as a string. + * @return The mobile network code. */ - public @Nullable String getMncString() { - return this.mMnc; + @Nullable + public String getMncString() { + return mMnc; } /** - * @return the ISO country code + * @return The ISO country code. Empty if not available. */ public String getCountryIso() { - return this.mCountryIso; + return mCountryIso; } - /** @return whether the subscription is an eUICC one. */ + /** + * @return {@code true} if the subscription is from eSIM. + */ public boolean isEmbedded() { - return this.mIsEmbedded; + return mIsEmbedded; } /** * An opportunistic subscription connects to a network that is * limited in functionality and / or coverage. * - * @return whether subscription is opportunistic. + * @return Whether subscription is opportunistic. */ public boolean isOpportunistic() { return mIsOpportunistic; @@ -617,23 +655,18 @@ public class SubscriptionInfo implements Parcelable { * Such that those subscriptions will have some affiliated behaviors such as opportunistic * subscription may be invisible to the user. * - * @return group UUID a String of group UUID if it belongs to a group. Otherwise - * it will return null. + * @return Group UUID a String of group UUID if it belongs to a group. Otherwise + * {@code null}. */ - public @Nullable ParcelUuid getGroupUuid() { - return mGroupUUID; - } - - /** - * @hide - */ - public void clearGroupUuid() { - this.mGroupUUID = null; + @Nullable + public ParcelUuid getGroupUuid() { + return mGroupUuid; } /** * @hide */ + @NonNull public List<String> getEhplmns() { return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns); } @@ -641,36 +674,45 @@ public class SubscriptionInfo implements Parcelable { /** * @hide */ + @NonNull public List<String> getHplmns() { return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns); } /** - * Return owner package of group the subscription belongs to. + * @return The owner package of group the subscription belongs to. * * @hide */ - public @Nullable String getGroupOwner() { + @NonNull + public String getGroupOwner() { return mGroupOwner; } /** - * @return the profile class of this subscription. + * @return The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return + * {@code false}). + * * @hide */ @SystemApi - public @SubscriptionManager.ProfileClass int getProfileClass() { - return this.mProfileClass; + @ProfileClass + public int getProfileClass() { + return mProfileClass; } /** * This method returns the type of a subscription. It can be * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. - * @return the type of subscription + * + * @return The type of the subscription. */ - public @SubscriptionManager.SubscriptionType int getSubscriptionType() { - return mSubscriptionType; + @SubscriptionType + public int getSubscriptionType() { + return mType; } /** @@ -679,7 +721,7 @@ public class SubscriptionInfo implements Parcelable { * returns true). * * @param context Context of the application to check. - * @return whether the app is authorized to manage this subscription per its metadata. + * @return Whether the app is authorized to manage this subscription per its metadata. * @hide * @deprecated - Do not use. */ @@ -700,7 +742,7 @@ public class SubscriptionInfo implements Parcelable { */ @Deprecated public boolean canManageSubscription(Context context, String packageName) { - List<UiccAccessRule> allAccessRules = getAllAccessRules(); + List<UiccAccessRule> allAccessRules = getAccessRules(); if (allAccessRules == null) { return false; } @@ -723,27 +765,17 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who - * is authorized to manage this subscription. - * TODO and fix it properly in R / master: either deprecate this and have 3 APIs - * native + carrier + all, or have this return all by default. + * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to + * manage this subscription. + * * @hide */ @SystemApi - public @Nullable List<UiccAccessRule> getAccessRules() { - if (mNativeAccessRules == null) return null; - return Arrays.asList(mNativeAccessRules); - } - - /** - * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs - * dictating who is authorized to manage this subscription. - * @hide - */ - public @Nullable List<UiccAccessRule> getAllAccessRules() { + @Nullable + public List<UiccAccessRule> getAccessRules() { List<UiccAccessRule> merged = new ArrayList<>(); if (mNativeAccessRules != null) { - merged.addAll(getAccessRules()); + merged.addAll(Arrays.asList(mNativeAccessRules)); } if (mCarrierConfigAccessRules != null) { merged.addAll(Arrays.asList(mCarrierConfigAccessRules)); @@ -762,50 +794,38 @@ public class SubscriptionInfo implements Parcelable { * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile * owner access is deprecated and will be removed in a future release. * - * @return the card string of the SIM card which contains the subscription or an empty string + * @return The card string of the SIM card which contains the subscription or an empty string * if these requirements are not met. The card string is the ICCID for UICCs or the EID for * eUICCs. + * * @hide - * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java */ + @NonNull public String getCardString() { - return this.mCardString; + return mCardString; } /** - * @hide - */ - public void clearCardString() { - this.mCardString = ""; - } - - /** - * Returns the card ID of the SIM card which contains the subscription (see - * {@link UiccCardInfo#getCardId()}. - * @return the cardId + * @return The card ID of the SIM card which contains the subscription. + * + * @see UiccCardInfo#getCardId(). */ public int getCardId() { - return this.mCardId; + return mCardId; } /** - * Returns the port index of the SIM card which contains the subscription. - * - * @return the portIndex + * @return The port index of the SIM card which contains the subscription. */ public int getPortIndex() { - return this.mPortIndex; - } - - /** - * Set whether the subscription's group is disabled. - * @hide - */ - public void setGroupDisabled(boolean isGroupDisabled) { - this.mIsGroupDisabled = isGroupDisabled; + return mPortIndex; } /** - * Return whether the subscription's group is disabled. + * @return {@code true} if the group of the subscription is disabled. This is only useful if + * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we + * should disable this opportunistic subscription. + * * @hide */ @SystemApi @@ -814,7 +834,7 @@ public class SubscriptionInfo implements Parcelable { } /** - * Return whether uicc applications are set to be enabled or disabled. + * @return {@code true} if Uicc applications are set to be enabled or disabled. * @hide */ @SystemApi @@ -825,56 +845,50 @@ public class SubscriptionInfo implements Parcelable { /** * Get the usage setting for this subscription. * - * @return the usage setting used for this subscription. + * @return The usage setting used for this subscription. */ - public @UsageSetting int getUsageSetting() { + @UsageSetting + public int getUsageSetting() { return mUsageSetting; } - public static final @android.annotation.NonNull - Parcelable.Creator<SubscriptionInfo> CREATOR = - new Parcelable.Creator<SubscriptionInfo>() { + @NonNull + public static final Parcelable.Creator<SubscriptionInfo> CREATOR = + new Parcelable.Creator<SubscriptionInfo>() { @Override public SubscriptionInfo createFromParcel(Parcel source) { - int id = source.readInt(); - String iccId = source.readString(); - int simSlotIndex = source.readInt(); - CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - int nameSource = source.readInt(); - int iconTint = source.readInt(); - String number = source.readString(); - int dataRoaming = source.readInt(); - String mcc = source.readString(); - String mnc = source.readString(); - String countryIso = source.readString(); - boolean isEmbedded = source.readBoolean(); - UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR); - String cardString = source.readString(); - int cardId = source.readInt(); - int portId = source.readInt(); - boolean isOpportunistic = source.readBoolean(); - String groupUUID = source.readString(); - boolean isGroupDisabled = source.readBoolean(); - int carrierid = source.readInt(); - int profileClass = source.readInt(); - int subType = source.readInt(); - String[] ehplmns = source.createStringArray(); - String[] hplmns = source.createStringArray(); - String groupOwner = source.readString(); - UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray( - UiccAccessRule.CREATOR); - boolean areUiccApplicationsEnabled = source.readBoolean(); - int usageSetting = source.readInt(); - - SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName, - carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null, - mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId, - isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType, - groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, - portId, usageSetting); - info.setAssociatedPlmns(ehplmns, hplmns); - return info; + return new Builder() + .setId(source.readInt()) + .setIccId(source.readString()) + .setSimSlotIndex(source.readInt()) + .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setNameSource(source.readInt()) + .setIconTint(source.readInt()) + .setNumber(source.readString()) + .setDataRoaming(source.readInt()) + .setMcc(source.readString()) + .setMnc(source.readString()) + .setCountryIso(source.readString()) + .setEmbedded(source.readBoolean()) + .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR)) + .setCardString(source.readString()) + .setCardId(source.readInt()) + .setPortIndex(source.readInt()) + .setOpportunistic(source.readBoolean()) + .setGroupUuid(source.readString8()) + .setGroupDisabled(source.readBoolean()) + .setCarrierId(source.readInt()) + .setProfileClass(source.readInt()) + .setType(source.readInt()) + .setEhplmns(source.createStringArray()) + .setHplmns(source.createStringArray()) + .setGroupOwner(source.readString()) + .setCarrierConfigAccessRules(source.createTypedArray( + UiccAccessRule.CREATOR)) + .setUiccApplicationsEnabled(source.readBoolean()) + .setUsageSetting(source.readInt()) + .build(); } @Override @@ -904,11 +918,11 @@ public class SubscriptionInfo implements Parcelable { dest.writeInt(mCardId); dest.writeInt(mPortIndex); dest.writeBoolean(mIsOpportunistic); - dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString()); + dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString()); dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); - dest.writeInt(mSubscriptionType); + dest.writeInt(mType); dest.writeStringArray(mEhplmns); dest.writeStringArray(mHplmns); dest.writeString(mGroupOwner); @@ -923,6 +937,11 @@ public class SubscriptionInfo implements Parcelable { } /** + * Get ICCID stripped PII information on user build. + * + * @param iccId The original ICCID. + * @return The stripped string. + * * @hide */ public static String givePrintableIccid(String iccId) { @@ -951,12 +970,12 @@ public class SubscriptionInfo implements Parcelable { + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " portIndex=" + mPortIndex - + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID + + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid + " isGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass + " ehplmns=" + Arrays.toString(mEhplmns) + " hplmns=" + Arrays.toString(mHplmns) - + " subscriptionType=" + mSubscriptionType + + " mType=" + mType + " groupOwner=" + mGroupOwner + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules) + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled @@ -966,7 +985,7 @@ public class SubscriptionInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, - mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, + mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules), mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting); @@ -974,16 +993,9 @@ public class SubscriptionInfo implements Parcelable { @Override public boolean equals(Object obj) { - if (obj == null) return false; - if (obj == this) return true; - - SubscriptionInfo toCompare; - try { - toCompare = (SubscriptionInfo) obj; - } catch (ClassCastException ex) { - return false; - } - + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + SubscriptionInfo toCompare = (SubscriptionInfo) obj; return mId == toCompare.mId && mSimSlotIndex == toCompare.mSimSlotIndex && mNameSource == toCompare.mNameSource @@ -994,7 +1006,7 @@ public class SubscriptionInfo implements Parcelable { && mIsGroupDisabled == toCompare.mIsGroupDisabled && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled && mCarrierId == toCompare.mCarrierId - && Objects.equals(mGroupUUID, toCompare.mGroupUUID) + && Objects.equals(mGroupUuid, toCompare.mGroupUuid) && Objects.equals(mIccId, toCompare.mIccId) && Objects.equals(mNumber, toCompare.mNumber) && Objects.equals(mMcc, toCompare.mMcc) @@ -1012,4 +1024,629 @@ public class SubscriptionInfo implements Parcelable { && Arrays.equals(mHplmns, toCompare.mHplmns) && mUsageSetting == toCompare.mUsageSetting; } + + /** + * The builder class of {@link SubscriptionInfo}. + * + * @hide + */ + public static class Builder { + /** + * The subscription id. + */ + private int mId = 0; + + /** + * The ICCID of the SIM that is associated with this subscription, empty if unknown. + */ + @NonNull + private String mIccId = ""; + + /** + * The index of the SIM slot that currently contains the subscription and not necessarily + * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the + * subscription is inactive. + */ + private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + */ + @NonNull + private CharSequence mDisplayName = ""; + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + */ + @NonNull + private CharSequence mCarrierName = ""; + + /** + * The source of the carrier name. + */ + @SimDisplayNameSource + private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID; + + /** + * The color to be used for tinting the icon when displaying to the user. + */ + private int mIconTint = 0; + + /** + * The number presented to the user identify this subscription. + */ + @NonNull + private String mNumber = ""; + + /** + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + */ + private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE; + + /** + * SIM icon bitmap cache. + */ + @Nullable + private Bitmap mIconBitmap = null; + + /** + * The mobile country code. + */ + @Nullable + private String mMcc = null; + + /** + * The mobile network code. + */ + @Nullable + private String mMnc = null; + + /** + * EHPLMNs associated with the subscription. + */ + @NonNull + private String[] mEhplmns = new String[0]; + + /** + * HPLMNs associated with the subscription. + */ + @NonNull + private String[] mHplmns = new String[0]; + + /** + * The ISO Country code for the subscription's provider. + */ + @NonNull + private String mCountryIso = ""; + + /** + * Whether the subscription is from eSIM. + */ + private boolean mIsEmbedded = false; + + /** + * The native access rules for this subscription, if it is embedded and defines any. This + * does not include access rules for non-embedded subscriptions. + */ + @Nullable + private UiccAccessRule[] mNativeAccessRules = null; + + /** + * The card string of the SIM card. + */ + @NonNull + private String mCardString = ""; + + /** + * The card ID of the SIM card which contains the subscription. + */ + private int mCardId = -1; + + /** + * Whether the subscription is opportunistic or not. + */ + private boolean mIsOpportunistic = false; + + /** + * The group UUID of the subscription group. + */ + @Nullable + private ParcelUuid mGroupUuid = null; + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + */ + private boolean mIsGroupDisabled = false; + + /** + * The carrier id. + * + * @see TelephonyManager#getSimCarrierId() + */ + private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; + + /** + * The profile class populated from the profile metadata if present. Otherwise, the profile + * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile + * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). + */ + @ProfileClass + private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET; + + /** + * The subscription type. + */ + @SubscriptionType + private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM; + + /** + * The owner package of group the subscription belongs to. + */ + @NonNull + private String mGroupOwner = ""; + + /** + * The carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + */ + @Nullable + private UiccAccessRule[] mCarrierConfigAccessRules = null; + + /** + * Whether Uicc applications are configured to enable or not. + */ + private boolean mAreUiccApplicationsEnabled = true; + + /** + * the port index of the Uicc card. + */ + private int mPortIndex = 0; + + /** + * Subscription's preferred usage setting. + */ + @UsageSetting + private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; + + /** + * Default constructor. + */ + public Builder() { + } + + /** + * Constructor from {@link SubscriptionInfo}. + * + * @param info The subscription info. + */ + public Builder(@NonNull SubscriptionInfo info) { + mId = info.mId; + mIccId = info.mIccId; + mSimSlotIndex = info.mSimSlotIndex; + mDisplayName = info.mDisplayName; + mCarrierName = info.mCarrierName; + mNameSource = info.mNameSource; + mIconTint = info.mIconTint; + mNumber = info.mNumber; + mDataRoaming = info.mDataRoaming; + mIconBitmap = info.mIconBitmap; + mMcc = info.mMcc; + mMnc = info.mMnc; + mEhplmns = info.mEhplmns; + mHplmns = info.mHplmns; + mCountryIso = info.mCountryIso; + mIsEmbedded = info.mIsEmbedded; + mNativeAccessRules = info.mNativeAccessRules; + mCardString = info.mCardString; + mCardId = info.mCardId; + mIsOpportunistic = info.mIsOpportunistic; + mGroupUuid = info.mGroupUuid; + mIsGroupDisabled = info.mIsGroupDisabled; + mCarrierId = info.mCarrierId; + mProfileClass = info.mProfileClass; + mType = info.mType; + mGroupOwner = info.mGroupOwner; + mCarrierConfigAccessRules = info.mCarrierConfigAccessRules; + mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled; + mPortIndex = info.mPortIndex; + mUsageSetting = info.mUsageSetting; + } + + /** + * Set the subscription id. + * + * @param id The subscription id. + * @return The builder. + */ + @NonNull + public Builder setId(int id) { + mId = id; + return this; + } + + /** + * Set the ICCID of the SIM that is associated with this subscription. + * + * @param iccId The ICCID of the SIM that is associated with this subscription. + * @return The builder. + */ + @NonNull + public Builder setIccId(@Nullable String iccId) { + mIccId = TextUtils.emptyIfNull(iccId); + return this; + } + + /** + * Set the SIM index of the slot that currently contains the subscription. Set to + * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive. + * + * @param simSlotIndex The SIM slot index. + * @return The builder. + */ + @NonNull + public Builder setSimSlotIndex(int simSlotIndex) { + mSimSlotIndex = simSlotIndex; + return this; + } + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + * + * @param displayName The display name. + * @return The builder. + */ + @NonNull + public Builder setDisplayName(@Nullable CharSequence displayName) { + mDisplayName = displayName == null ? "" : displayName; + return this; + } + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @param carrierName The carrier name. + * @return The builder. + */ + @NonNull + public Builder setCarrierName(@Nullable CharSequence carrierName) { + mCarrierName = carrierName == null ? "" : carrierName; + return this; + } + + /** + * Set the source of the carrier name. + * + * @param nameSource The source of the carrier name. + * @return The builder. + */ + @NonNull + public Builder setNameSource(@SimDisplayNameSource int nameSource) { + mNameSource = nameSource; + return this; + } + + /** + * Set the color to be used for tinting the icon when displaying to the user. + * + * @param iconTint The color to be used for tinting the icon when displaying to the user. + * @return The builder. + */ + @NonNull + public Builder setIconTint(int iconTint) { + mIconTint = iconTint; + return this; + } + + /** + * Set the number presented to the user identify this subscription. + * + * @param number the number presented to the user identify this subscription. + * @return The builder. + */ + @NonNull + public Builder setNumber(@Nullable String number) { + mNumber = TextUtils.emptyIfNull(number); + return this; + } + + /** + * Set whether user enables data roaming for this subscription or not. + * + * @param dataRoaming Data roaming mode. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE} + * @return The builder. + */ + @NonNull + public Builder setDataRoaming(int dataRoaming) { + mDataRoaming = dataRoaming; + return this; + } + + /** + * Set SIM icon bitmap cache. + * + * @param iconBitmap SIM icon bitmap cache. + * @return The builder. + */ + @NonNull + public Builder setIcon(@Nullable Bitmap iconBitmap) { + mIconBitmap = iconBitmap; + return this; + } + + /** + * Set the mobile country code. + * + * @param mcc The mobile country code. + * @return The builder. + */ + @NonNull + public Builder setMcc(@Nullable String mcc) { + mMcc = mcc; + return this; + } + + /** + * Set the mobile network code. + * + * @param mnc Mobile network code. + * @return The builder. + */ + @NonNull + public Builder setMnc(@Nullable String mnc) { + mMnc = mnc; + return this; + } + + /** + * Set EHPLMNs associated with the subscription. + * + * @param ehplmns EHPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setEhplmns(@Nullable String[] ehplmns) { + mEhplmns = ehplmns == null ? new String[0] : ehplmns; + return this; + } + + /** + * Set HPLMNs associated with the subscription. + * + * @param hplmns HPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setHplmns(@Nullable String[] hplmns) { + mHplmns = hplmns == null ? new String[0] : hplmns; + return this; + } + + /** + * Set the ISO Country code for the subscription's provider. + * + * @param countryIso The ISO Country code for the subscription's provider. + * @return The builder. + */ + @NonNull + public Builder setCountryIso(@Nullable String countryIso) { + mCountryIso = TextUtils.emptyIfNull(countryIso); + return this; + } + + /** + * Set whether the subscription is from eSIM or not. + * + * @param isEmbedded {@code true} if the subscription is from eSIM. + * @return The builder. + */ + @NonNull + public Builder setEmbedded(boolean isEmbedded) { + mIsEmbedded = isEmbedded; + return this; + } + + /** + * Set the native access rules for this subscription, if it is embedded and defines any. + * This does not include access rules for non-embedded subscriptions. + * + * @param nativeAccessRules The native access rules for this subscription. + * @return The builder. + */ + @NonNull + public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) { + mNativeAccessRules = nativeAccessRules; + return this; + } + + /** + * Set the card string of the SIM card. + * + * @param cardString The card string of the SIM card. + * @return The builder. + * + * @see #getCardString() + */ + @NonNull + public Builder setCardString(@Nullable String cardString) { + mCardString = TextUtils.emptyIfNull(cardString); + return this; + } + + /** + * Set the card ID of the SIM card which contains the subscription. + * + * @param cardId The card ID of the SIM card which contains the subscription. + * @return The builder. + */ + @NonNull + public Builder setCardId(int cardId) { + mCardId = cardId; + return this; + } + + /** + * Set whether the subscription is opportunistic or not. + * + * @param isOpportunistic {@code true} if the subscription is opportunistic. + * @return The builder. + */ + @NonNull + public Builder setOpportunistic(boolean isOpportunistic) { + mIsOpportunistic = isOpportunistic; + return this; + } + + /** + * Set the group UUID of the subscription group. + * + * @param groupUuid The group UUID. + * @return The builder. + * + * @see #getGroupUuid() + */ + @NonNull + public Builder setGroupUuid(@Nullable String groupUuid) { + mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); + return this; + } + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + * + * @param isGroupDisabled {@code true} if group of the subscription is disabled. + * @return The builder. + */ + @NonNull + public Builder setGroupDisabled(boolean isGroupDisabled) { + mIsGroupDisabled = isGroupDisabled; + return this; + } + + /** + * Set the subscription carrier id. + * + * @param carrierId The carrier id. + * @return The builder + * + * @see TelephonyManager#getSimCarrierId() + */ + @NonNull + public Builder setCarrierId(int carrierId) { + mCarrierId = carrierId; + return this; + } + + /** + * Set the profile class populated from the profile metadata if present. + * + * @param profileClass the profile class populated from the profile metadata if present. + * @return The builder + * + * @see #getProfileClass() + */ + @NonNull + public Builder setProfileClass(@ProfileClass int profileClass) { + mProfileClass = profileClass; + return this; + } + + /** + * Set the subscription type. + * + * @param type Subscription type. + * @return The builder. + */ + @NonNull + public Builder setType(@SubscriptionType int type) { + mType = type; + return this; + } + + /** + * Set the owner package of group the subscription belongs to. + * + * @param groupOwner Owner package of group the subscription belongs to. + * @return The builder. + */ + @NonNull + public Builder setGroupOwner(@Nullable String groupOwner) { + mGroupOwner = TextUtils.emptyIfNull(groupOwner); + return this; + } + + /** + * Set the carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + * + * @param carrierConfigAccessRules The carrier certificates for this subscription + * @return The builder. + */ + @NonNull + public Builder setCarrierConfigAccessRules( + @Nullable UiccAccessRule[] carrierConfigAccessRules) { + mCarrierConfigAccessRules = carrierConfigAccessRules; + return this; + } + + /** + * Set whether Uicc applications are configured to enable or not. + * + * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to + * enable. + * @return The builder. + */ + @NonNull + public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) { + mAreUiccApplicationsEnabled = uiccApplicationsEnabled; + return this; + } + + /** + * Set the port index of the Uicc card. + * + * @param portIndex The port index of the Uicc card. + * @return The builder. + */ + @NonNull + public Builder setPortIndex(int portIndex) { + mPortIndex = portIndex; + return this; + } + + /** + * Set subscription's preferred usage setting. + * + * @param usageSetting Subscription's preferred usage setting. + * @return The builder. + */ + @NonNull + public Builder setUsageSetting(@UsageSetting int usageSetting) { + mUsageSetting = usageSetting; + return this; + } + + /** + * Build the {@link SubscriptionInfo}. + * + * @return The {@link SubscriptionInfo} instance. + */ + public SubscriptionInfo build() { + return new SubscriptionInfo(this); + } + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 6bc14bfd0cc2..1871ba62bb0e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -509,18 +509,14 @@ public class SubscriptionManager { public static final String TP_MESSAGE_REF = SimInfo.COLUMN_TP_MESSAGE_REF; /** - * TelephonyProvider column name data_enabled_override_rules. - * It's a list of rules for overriding data enabled settings. The syntax is - * For example, "mms=nonDefault" indicates enabling data for mms in non-default subscription. - * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default - * subscription and while is in voice call. + * TelephonyProvider column name enabled_mobile_data_policies. + * A list of mobile data policies, each of which represented by an integer and joint by ",". * * Default value is empty string. - * * @hide */ - public static final String DATA_ENABLED_OVERRIDE_RULES = - SimInfo.COLUMN_DATA_ENABLED_OVERRIDE_RULES; + public static final String ENABLED_MOBILE_DATA_POLICIES = + SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -1071,6 +1067,13 @@ public class SubscriptionManager { public static final String ALLOWED_NETWORK_TYPES = SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS; + /** + * TelephonyProvider column name for user handle associated with a sim. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String USER_HANDLE = SimInfo.COLUMN_USER_HANDLE; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"USAGE_SETTING_"}, @@ -3099,7 +3102,7 @@ public class SubscriptionManager { @SystemApi public boolean canManageSubscription(@NonNull SubscriptionInfo info, @NonNull String packageName) { - if (info == null || info.getAllAccessRules() == null || packageName == null) { + if (info == null || info.getAccessRules() == null || packageName == null) { return false; } PackageManager packageManager = mContext.getPackageManager(); @@ -3111,7 +3114,7 @@ public class SubscriptionManager { logd("Unknown package: " + packageName); return false; } - for (UiccAccessRule rule : info.getAllAccessRules()) { + for (UiccAccessRule rule : info.getAccessRules()) { if (rule.getCarrierPrivilegeStatus(packageInfo) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 12b4114930ca..8818ac2f6ee0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -191,9 +191,6 @@ public class TelephonyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L; - // Null IMEI anomaly uuid - private static final UUID IMEI_ANOMALY_UUID = UUID.fromString( - "83905f14-6455-450c-be29-8206f0427fe9"); /** * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)} * into the ResultReceiver Bundle. @@ -2184,11 +2181,7 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei() { - String imei = getImei(getSlotIndex()); - if (imei == null) { - AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null."); - } - return imei; + return getImei(getSlotIndex()); } /** @@ -2231,10 +2224,7 @@ public class TelephonyManager { @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei(int slotIndex) { ITelephony telephony = getITelephony(); - if (telephony == null) { - AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null"); - return null; - } + if (telephony == null) return null; try { return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag()); @@ -17122,6 +17112,266 @@ public class TelephonyManager { } /** + * A premium capability boosting the network to allow real-time interactive traffic. + * Corresponds to NetworkCapabilities#NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC. + */ + // TODO(b/245748544): add @link once NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC is defined. + public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1; + + /** + * Purchasable premium capabilities. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "PREMIUM_CAPABILITY_" }, value = { + PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC}) + public @interface PremiumCapability {} + + /** + * Returns the premium capability {@link PremiumCapability} as a String. + * + * @param capability The premium capability. + * @return The premium capability as a String. + * @hide + */ + public static String convertPremiumCapabilityToString(@PremiumCapability int capability) { + switch (capability) { + case PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC: + return "REALTIME_INTERACTIVE_TRAFFIC"; + default: + return "UNKNOWN (" + capability + ")"; + } + } + + /** + * Check whether the given premium capability is available for purchase from the carrier. + * If this is {@code true}, the capability can be purchased from the carrier using + * {@link #purchasePremiumCapability(int, Executor, Consumer)}. + * + * @param capability The premium capability to check. + * @return Whether the given premium capability is available to purchase. + * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE. + */ + @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) + public boolean isPremiumCapabilityAvailableForPurchase(@PremiumCapability int capability) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + throw new IllegalStateException("telephony service is null."); + } + return telephony.isPremiumCapabilityAvailableForPurchase(capability, getSubId()); + } catch (RemoteException ex) { + ex.rethrowAsRuntimeException(); + } + return false; + } + + /** + * Purchase premium capability request was successful. Subsequent attempts will return + * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires. + * The expiry time is determined by the type or duration of boost purchased from the carrier, + * provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; + + /** + * Purchase premium capability failed because the request is throttled for the amount of time + * specified by {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * or {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}. + * Subsequent attempts will return the same error until the request is no longer throttled + * or throttling conditions change. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; + + /** + * Purchase premium capability failed because it is already purchased and available. + * Subsequent attempts will return the same error until the booster expires. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; + + /** + * Purchase premium capability failed because a request was already made and is in progress. + * This may have been requested by either the same app or another app. + * Subsequent attempts will return the same error until the previous request completes. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4; + + /** + * Purchase premium capability failed because the user disabled the feature. + * Subsequent attempts will return the same error until the user re-enables the feature. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 5; + + /** + * Purchase premium capability failed because the user canceled the operation. + * Subsequent attempts will be throttled for the amount of time specified by + * {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; + + /** + * Purchase premium capability failed because the carrier disabled or does not support + * the capability, as specified in + * {@link CarrierConfigManager#KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY}. + * Subsequent attempts will return the same error until the carrier enables the feature. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; + + /** + * Purchase premium capability failed because the carrier app did not indicate success. + * Subsequent attempts will be throttled for the amount of time specified by + * {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; + + /** + * Purchase premium capability failed because we did not receive a response from the user + * for the booster notification within the time specified by + * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}. + * The booster notification will be automatically dismissed and subsequent attempts will be + * throttled for the amount of time specified by + * {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; + + /** + * Purchase premium capability failed because the device does not support the feature. + * Subsequent attempts will return the same error. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; + + /** + * Purchase premium capability failed because the telephony service is down or unavailable. + * Subsequent attempts will return the same error until request conditions are satisfied. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; + + /** + * Purchase premium capability failed because the network is not available. + * Subsequent attempts will return the same error until network conditions change. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; + + /** + * Purchase premium capability failed because the network is congested. + * Subsequent attempts will be throttled for the amount of time specified by + * {@link CarrierConfigManager + * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} + * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + * Throttling will be reevaluated when the network is no longer congested. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; + + /** + * Results of the purchase premium capability request. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "PURCHASE_PREMIUM_CAPABILITY_RESULT_" }, value = { + PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, + PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS, + PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, + PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, + PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE, + PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED}) + public @interface PurchasePremiumCapabilityResult {} + + /** + * Returns the purchase result {@link PurchasePremiumCapabilityResult} as a String. + * + * @param result The purchase premium capability result. + * @return The purchase result as a String. + * @hide + */ + public static String convertPurchaseResultToString( + @PurchasePremiumCapabilityResult int result) { + switch (result) { + case PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS: + return "SUCCESS"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED: + return "THROTTLED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED: + return "ALREADY_PURCHASED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS: + return "ALREADY_IN_PROGRESS"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED: + return "USER_DISABLED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED: + return "USER_CANCELED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED: + return "CARRIER_DISABLED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR: + return "CARRIER_ERROR"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT: + return "TIMEOUT"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED: + return "FEATURE_NOT_SUPPORTED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED: + return "REQUEST_FAILED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE: + return "NETWORK_NOT_AVAILABLE"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED: + return "NETWORK_CONGESTED"; + default: + return "UNKNOWN (" + result + ")"; + } + } + + /** + * Purchase the given premium capability from the carrier. + * This requires user action to purchase the boost from the carrier. + * If this returns {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS} or + * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}, applications can request + * the premium capability via {@link ConnectivityManager#requestNetwork}. + * + * @param capability The premium capability to purchase. + * @param executor The callback executor for the response. + * @param callback The result of the purchase request. + * One of {@link PurchasePremiumCapabilityResult}. + * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE. + * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid + */ + @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) + public void purchasePremiumCapability(@PremiumCapability int capability, + @NonNull @CallbackExecutor Executor executor, + @NonNull @PurchasePremiumCapabilityResult Consumer<Integer> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> callback.accept(result)); + } + }; + + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + callback.accept(PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED); + return; + } + telephony.purchasePremiumCapability(capability, internalCallback, getSubId()); + } catch (RemoteException ex) { + callback.accept(PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED); + } + } + + /** * Get last known cell identity. * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException. diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 700d61597d00..d8b2cbebdf28 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -725,7 +725,7 @@ public abstract class DataService extends Service { @Override public void onDestroy() { - mHandlerThread.quit(); + mHandlerThread.quitSafely(); super.onDestroy(); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 850d26801f17..648866b6662d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2529,6 +2529,16 @@ interface ITelephony { void getSlicingConfig(in ResultReceiver callback); /** + * Check whether the given premium capability is available for purchase from the carrier. + */ + boolean isPremiumCapabilityAvailableForPurchase(int capability, int subId); + + /** + * Purchase the given premium capability from the carrier. + */ + void purchasePremiumCapability(int capability, IIntegerConsumer callback, int subId); + + /** * Register an IMS connection state callback */ void registerImsStateCallback(int subId, int feature, in IImsStateCallback cb, diff --git a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java index 2e515705a253..761efe4a8484 100644 --- a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java +++ b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java @@ -110,7 +110,7 @@ public class FixVibrateSetting extends Activity implements View.OnClickListener private void test() { Intent intent = new Intent(this, FixVibrateSetting.class); - PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0); + PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(this) .setSmallIcon(R.drawable.stat_sys_warning) diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml index e173eba0a393..487a0c3bd465 100644 --- a/tests/FlickerTests/AndroidManifest.xml +++ b/tests/FlickerTests/AndroidManifest.xml @@ -40,6 +40,8 @@ <uses-permission android:name="android.permission.READ_LOGS"/> <!-- ATM.removeRootTasksWithActivityTypes() --> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- ActivityOptions.makeCustomTaskAnimation() --> + <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <!-- Allow the test to write directly to /sdcard/ --> <application android:requestLegacyExternalStorage="true"> <uses-library android:name="android.test.runner"/> diff --git a/services/tests/wmtests/res/values/styles.xml b/tests/FlickerTests/res/anim/show_2000ms.xml index 6857ff99e9b8..76e375f12e31 100644 --- a/services/tests/wmtests/res/values/styles.xml +++ b/tests/FlickerTests/res/anim/show_2000ms.xml @@ -1,3 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> <!-- ~ Copyright (C) 2022 The Android Open Source Project ~ @@ -14,11 +15,7 @@ ~ limitations under the License. --> -<resources> - <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault"> - <item name="android:windowNoTitle">true</item> - <item name="android:windowFullscreen">true</item> - <item name="android:windowIsTranslucent">true</item> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> -</resources> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="2000" + android:fromXDelta="0" + android:toXDelta="0" />
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 1e798f3ed12e..f77ec8a4a853 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -21,13 +21,14 @@ import android.platform.test.annotations.Presubmit import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume import org.junit.Test /** - * Base test class containing common assertions for [ComponentMatcher.NAV_BAR], - * [ComponentMatcher.TASK_BAR], [ComponentMatcher.STATUS_BAR], and general assertions + * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR], + * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions * (layers visible in consecutive states, entire screen covered, etc.) */ abstract class BaseTest @JvmOverloads constructor( @@ -72,7 +73,7 @@ abstract class BaseTest @JvmOverloads constructor( open fun entireScreenCovered() = testSpec.entireScreenCovered() /** - * Checks that the [ComponentMatcher.NAV_BAR] layer is visible during the whole transition + * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition * * Note: Phones only */ @@ -84,7 +85,8 @@ abstract class BaseTest @JvmOverloads constructor( } /** - * Checks the position of the [ComponentMatcher.NAV_BAR] at the start and end of the transition + * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the + * transition * * Note: Phones only */ @@ -96,7 +98,7 @@ abstract class BaseTest @JvmOverloads constructor( } /** - * Checks that the [ComponentMatcher.NAV_BAR] window is visible during the whole transition + * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition * * Note: Phones only */ @@ -108,8 +110,8 @@ abstract class BaseTest @JvmOverloads constructor( } /** - * Checks that the [ComponentMatcher.TASK_BAR] window is visible at the start and end of the - * transition + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of + * the transition * * Note: Large screen only */ @@ -121,7 +123,8 @@ abstract class BaseTest @JvmOverloads constructor( } /** - * Checks that the [ComponentMatcher.TASK_BAR] window is visible during the whole transition + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole + * transition * * Note: Large screen only */ @@ -133,7 +136,7 @@ abstract class BaseTest @JvmOverloads constructor( } /** - * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the start and end + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end * of the transition */ @Presubmit @@ -142,7 +145,7 @@ abstract class BaseTest @JvmOverloads constructor( testSpec.statusBarLayerIsVisibleAtStartAndEnd() /** - * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the * transition */ @Presubmit @@ -150,7 +153,8 @@ abstract class BaseTest @JvmOverloads constructor( open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd() /** - * Checks that the [ComponentMatcher.STATUS_BAR] window is visible during the whole transition + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole + * transition */ @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index b8fe9f9df882..ef5cec29f898 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -33,12 +33,12 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe import org.junit.Assume.assumeNotNull class ActivityEmbeddingAppHelper @JvmOverloads constructor( - instr: Instrumentation, - launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME, - component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT, - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + instr: Instrumentation, + launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL, + component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT, + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { /** @@ -47,8 +47,8 @@ class ActivityEmbeddingAppHelper @JvmOverloads constructor( */ fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) { val launchButton = uiDevice.wait( - Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")), - FIND_TIMEOUT) + Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")), + FIND_TIMEOUT) require(launchButton != null) { "Can't find launch placeholder split button on screen." } @@ -62,14 +62,15 @@ class ActivityEmbeddingAppHelper @JvmOverloads constructor( companion object { private const val TAG = "ActivityEmbeddingAppHelper" - val MAIN_ACTIVITY_COMPONENT = ActivityOptions - .ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + val MAIN_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent() - val PLACEHOLDER_PRIMARY_COMPONENT = ActivityOptions - .ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + val PLACEHOLDER_PRIMARY_COMPONENT = + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT + .toFlickerComponent() - val PLACEHOLDER_SECONDARY_COMPONENT = ActivityOptions - .ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME + val PLACEHOLDER_SECONDARY_COMPONENT = + ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT .toFlickerComponent() @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt index 826cc2ef16d6..4ff4e316476b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.helpers +package com.android.server.wm.flicker.helpers import android.app.Instrumentation import com.android.server.wm.traces.common.ComponentNameMatcher @@ -23,4 +23,4 @@ class AppPairsHelper( instrumentation: Instrumentation, activityLabel: String, component: ComponentNameMatcher -) : BaseAppHelper(instrumentation, activityLabel, component) +) : StandardAppHelper(instrumentation, activityLabel, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt index b696fc30c19f..132e7b6fef6d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -24,11 +24,11 @@ import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class FixedOrientationAppHelper @JvmOverloads constructor( - instr: Instrumentation, - launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME, - component: ComponentNameMatcher = - ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy - ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) + instr: Instrumentation, + launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt index ea5a5f8b3927..2d81e0d9e165 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -29,9 +29,8 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe class GameAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME, - component: ComponentNameMatcher = - ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherName: String = ActivityOptions.Game.LABEL, + component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory.getInstance(instr).launcherStrategy, ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index e01cceb5d636..b5b0da934ad9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -32,9 +32,9 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( instr: Instrumentation, private val rotation: Int, private val imePackageName: String = IME_PACKAGE, - launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME, + launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME(wmHelper: WindowManagerStateHelper) { // do nothing (the app is focused automatically) @@ -62,21 +62,22 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { val button = uiDevice.wait(Until.findObject(By.res(getPackage(), - "start_dialog_themed_activity_btn")), FIND_TIMEOUT) + "start_dialog_themed_activity_btn")), FIND_TIMEOUT) requireNotNull(button) { "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. Screen turned off)" + "was left in an unknown state (e.g. Screen turned off)" } button.click() wmHelper.StateSyncBuilder() .withFullScreenApp( - ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent()) .waitForAndVerify() } + fun dismissDialog(wmHelper: WindowManagerStateHelper) { val dialog = uiDevice.wait( - Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) + Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) // Pressing back key to dismiss the dialog if (dialog != null) { @@ -86,9 +87,10 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( .waitForAndVerify() } } + fun getInsetsVisibleFromDialog(type: Int): Boolean { val insetsVisibilityTextView = uiDevice.wait( - Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) + Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) if (insetsVisibilityTextView != null) { val visibility = insetsVisibilityTextView.text.toString() val matcher = when (type) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index b672b1bc0da5..56b6b92dd6ef 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -28,12 +28,11 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe open class ImeAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.Ime.Default.LABEL, component: ComponentNameMatcher = - ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { /** * Opens the IME and wait for it to be displayed @@ -73,8 +72,8 @@ open class ImeAppHelper @JvmOverloads constructor( open fun finishActivity(wmHelper: WindowManagerStateHelper) { val finishButton = uiDevice.wait( - Until.findObject(By.res(getPackage(), "finish_activity_btn")), - FIND_TIMEOUT) + Until.findObject(By.res(getPackage(), "finish_activity_btn")), + FIND_TIMEOUT) requireNotNull(finishButton) { "Finish activity button not found, probably IME activity is not on the screen?" } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt index df47e9dfacd9..bfb68da20836 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt @@ -26,16 +26,16 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe class ImeEditorPopupDialogAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.Ime.EditorPopupDialogActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + ActivityOptions.Ime.EditorPopupDialogActivity.COMPONENT.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME(wmHelper: WindowManagerStateHelper) { val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT) - require(editText != null) { + requireNotNull(editText) { "Text field not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } editText.click() waitIMEShown(wmHelper) @@ -43,7 +43,7 @@ class ImeEditorPopupDialogAppHelper @JvmOverloads constructor( fun dismissDialog(wmHelper: WindowManagerStateHelper) { val dismissButton = uiDevice.wait( - Until.findObject(By.text("Dismiss")), FIND_TIMEOUT) + Until.findObject(By.text("Dismiss")), FIND_TIMEOUT) // Pressing back key to dismiss the dialog if (dismissButton != null) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt index d3945c1749e3..3b8d3c3440a8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent class ImeStateInitializeHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME, + launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt index 4d0fbc4a0e38..cb54b5732c8d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.helpers +package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.testapp.Components -class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper( +class LaunchBubbleHelper(instrumentation: Instrumentation) : StandardAppHelper( instrumentation, - Components.SimpleActivity.LABEL, - Components.SimpleActivity.COMPONENT.toFlickerComponent() -)
\ No newline at end of file + ActivityOptions.Bubbles.LaunchBubble.LABEL, + ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent() +) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt index 807e6729c2d0..dde0b3e6873d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -28,13 +28,13 @@ import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent class MailAppHelper @JvmOverloads constructor( - instr: Instrumentation, - launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME, - component: ComponentNameMatcher = - ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + instr: Instrumentation, + launcherName: String = ActivityOptions.Mail.LABEL, + component: ComponentNameMatcher = + ActivityOptions.Mail.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { fun openMail(rowIdx: Int) { @@ -46,7 +46,7 @@ class MailAppHelper @JvmOverloads constructor( if (row != null) break scrollDown() } - require(row != null) {""} + require(row != null) { "" } row.click() uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT) } @@ -57,9 +57,9 @@ class MailAppHelper @JvmOverloads constructor( } fun waitForMailList(): UiObject2 { - var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) + val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT) - require(ret != null) {""} + requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" } return ret } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt index 245a82f938b3..9bdfadb35ff3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt @@ -14,20 +14,31 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.helpers +package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.content.Context import android.provider.Settings +import android.util.Log +import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.traces.common.ComponentNameMatcher +import java.io.IOException -class MultiWindowHelper( +class MultiWindowUtils( instrumentation: Instrumentation, activityLabel: String, componentsInfo: ComponentNameMatcher -) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { +) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { + fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { + try { + SystemUtil.runShellCommand(instrumentation, cmd) + } catch (e: IOException) { + Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e") + } + } + fun getDevEnableNonResizableMultiWindow(context: Context): Int = Settings.Global.getInt(context.contentResolver, Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt index 9fb574cf1c04..f3386afa610c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -29,9 +29,9 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe class NewTasksAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.LaunchNewTask.LABEL, component: ComponentNameMatcher = - ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy @@ -43,7 +43,7 @@ class NewTasksAppHelper @JvmOverloads constructor( requireNotNull(button) { "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() wmHelper.StateSyncBuilder() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt index a1dbeeaef5cc..19ce3ba21812 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent class NonResizeableAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.NonResizeableActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt index b031a4523ab0..97642f1617af 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt @@ -28,25 +28,28 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe class NotificationAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.Notification.LABEL, component: ComponentNameMatcher = - ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + ActivityOptions.Notification.COMPONENT.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + .getInstance(instr) + .launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { fun postNotification(wmHelper: WindowManagerStateHelper) { val button = uiDevice.wait( - Until.findObject(By.res(getPackage(), "post_notification")), - FIND_TIMEOUT) + Until.findObject(By.res(getPackage(), "post_notification")), + FIND_TIMEOUT) - require(button != null) { + requireNotNull(button) { "Post notification button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT) - ?: error("Flicker Notification not found") + ?: error("Flicker Notification not found") + wmHelper.StateSyncBuilder() + .withAppTransitionIdle() + .waitForAndVerify() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index bdc05e7ecb52..4d801c9032cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -14,28 +14,23 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.helpers +package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.media.session.MediaController import android.media.session.MediaSessionManager import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.helpers.FIND_TIMEOUT -import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.Rect import com.android.server.wm.traces.common.WindowManagerConditionsFactory import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow -import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild -import com.android.wm.shell.flicker.testapp.Components -class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( +open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper( instrumentation, - Components.PipActivity.LABEL, - Components.PipActivity.COMPONENT.toFlickerComponent() + ActivityOptions.Pip.LABEL, + ActivityOptions.Pip.COMPONENT.toFlickerComponent() ) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) @@ -46,16 +41,11 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( it.packageName == `package` } - fun clickObject(resId: String) { + open fun clickObject(resId: String) { val selector = By.res(`package`, resId) val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object") - if (!isTelevision) { - obj.click() - } else { - focusOnObject(selector) || error("Could not focus on `$resId` object") - uiDevice.pressDPadCenter() - } + obj.click() } /** @@ -85,20 +75,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = launchViaIntentAndWaitShown(wmHelper) - private fun focusOnObject(selector: BySelector): Boolean { - // We expect all the focusable UI elements to be arranged in a way so that it is possible - // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top" - // from "the bottom". - repeat(FOCUS_ATTEMPTS) { - uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true } - ?: error("The object we try to focus on is gone.") - - uiDevice.pressDPadDown() - uiDevice.waitForIdle() - } - return false - } - fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) { clickObject(ENTER_PIP_BUTTON_ID) @@ -140,12 +116,8 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( "Use PipAppHelper.closePipWindow(wmHelper) instead", ReplaceWith("closePipWindow(wmHelper)") ) - fun closePipWindow() { - if (isTelevision) { - uiDevice.closeTvPipWindow() - } else { - closePipWindow(WindowManagerStateHelper(mInstrumentation)) - } + open fun closePipWindow() { + closePipWindow(WindowManagerStateHelper(mInstrumentation)) } private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { @@ -159,20 +131,16 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( /** * Taps the pip window and dismisses it by clicking on the X button. */ - fun closePipWindow(wmHelper: WindowManagerStateHelper) { - if (isTelevision) { - uiDevice.closeTvPipWindow() - } else { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - val dismissPipObject = uiDevice.findObject(dismissSelector) - ?: error("PIP window dismiss button not found") - val dismissButtonBounds = dismissPipObject.visibleBounds - uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) - } + open fun closePipWindow(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // search and interact with the dismiss button + val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") + uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) + val dismissPipObject = uiDevice.findObject(dismissSelector) + ?: error("PIP window dismiss button not found") + val dismissButtonBounds = dismissPipObject.visibleBounds + uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) // Wait for animation to complete. wmHelper.StateSyncBuilder() @@ -213,7 +181,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } companion object { - private const val FOCUS_ATTEMPTS = 20 private const val ENTER_PIP_BUTTON_ID = "enter_pip" private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 6d466d72d7dd..fc1ff7c84675 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent class SeamlessRotationAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.SeamlessRotation.LABEL, component: ComponentNameMatcher = - ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt index 804ab3864591..0e1df411d394 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt @@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent class ShowWhenLockedAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index 5da273a7f1dc..e3461a74b4ce 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent class SimpleAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.SimpleActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index 060e9af4a51c..f4ea37f2127a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -29,16 +29,15 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe class TwoActivitiesAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME, + launcherName: String = ActivityOptions.LaunchNewActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy + ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { private val secondActivityComponent = - ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) { val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY) @@ -46,7 +45,7 @@ class TwoActivitiesAppHelper @JvmOverloads constructor( requireNotNull(button) { "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index f7e5b239ec29..31fcc6a2fd45 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -26,11 +26,9 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.ComponentNameMatcher -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -113,30 +111,11 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Presubmit @Test - fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible() @Presubmit @Test - fun imeAppWindowVisibilityLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) - // the app starts visible in live tile, and stays visible for the duration of entering - // and exiting overview. However, legacy transitions seem to have a bug which causes - // everything to restart during the test, so expect the app to disappear and come back. - // Since we log 1x per frame, sometimes the activity visibility and the app visibility - // are updated together, sometimes not, thus ignore activity check at the start - testSpec.assertWm { - this.isAppWindowVisible(testApp) - .then() - .isAppWindowInvisible(testApp) - .then() - .isAppWindowVisible(testApp) - } - } - - @Presubmit - @Test - fun imeAppWindowIsAlwaysVisibleShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) + fun imeAppWindowIsAlwaysVisible() { // the app starts visible in live tile, and stays visible for the duration of entering // and exiting overview. Since we log 1x per frame, sometimes the activity visibility // and the app visibility are updated together, sometimes not, thus ignore activity @@ -148,21 +127,7 @@ open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSp @Presubmit @Test - fun imeLayerIsBecomesVisibleLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) - testSpec.assertLayers { - this.isVisible(ComponentNameMatcher.IME) - .then() - .isInvisible(ComponentNameMatcher.IME) - .then() - .isVisible(ComponentNameMatcher.IME) - } - } - - @Presubmit - @Test - fun imeLayerBecomesVisibleShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) + fun imeLayerBecomesVisible() { testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 08d7be2ac662..f019acc29316 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -83,19 +83,17 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() /** - * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at - * the start of the transition, that - * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the - * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible - * at the end + * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at + * the start of the transition, that [ActivityOptions.SimpleActivity] becomes visible during + * the transition, and that [ActivityOptions.LaunchNewActivity] is again visible at the end */ @Presubmit @Test fun finishSubActivity() { - val buttonActivityComponent = ActivityOptions - .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent() - val imeAutoFocusActivityComponent = ActivityOptions - .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + val buttonActivityComponent = + ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent() + val imeAutoFocusActivityComponent = + ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() testSpec.assertWm { this.isAppWindowOnTop(buttonActivityComponent) .then() @@ -106,7 +104,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp } /** - * Checks that the [ComponentMatcher.LAUNCHER] window is not on top. The launcher cannot be + * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name, * and both are never simultaneously visible */ @@ -119,7 +117,7 @@ class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSp } /** - * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible during the transition + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition */ @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt new file mode 100644 index 000000000000..4f21412a1959 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch + +import android.app.Instrumentation +import android.os.Bundle +import android.os.Handler +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.R +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule +import com.android.server.wm.traces.common.ComponentNameMatcher +import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test the [android.app.ActivityOptions.makeCustomTaskAnimation]. + * + * To run this test: `atest FlickerTests:OverrideTaskTransitionTest` + * + * Actions: + * Launches SimpleActivity with alpha_2000ms animation + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + device.wakeUpAndGoToHomeScreen() + RemoveAllTasksButHomeRule.removeAllTasksButHome() + setRotation(testSpec.startRotation) + } + transitions { + instrumentation.context.startActivity( + testApp.openAppIntent, createCustomTaskAnimation()) + wmHelper.StateSyncBuilder() + .add(WindowManagerConditionsFactory.isWMStateComplete()) + .withAppTransitionIdle() + .withWindowSurfaceAppeared(testApp) + .waitForAndVerify() + } + teardown { + testApp.exit() + } + } + } + + @Presubmit + @Test + fun testSimpleActivityIsShownDirectly() { + testSpec.assertLayers { + isVisible(ComponentNameMatcher.LAUNCHER) + .isInvisible(ComponentNameMatcher.SPLASH_SCREEN) + .isInvisible(testApp) + .then() + // The custom animation should block the entire launcher from the very beginning + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + private fun createCustomTaskAnimation(): Bundle { + return android.app.ActivityOptions.makeCustomTaskAnimation(instrumentation.context, + R.anim.show_2000ms, 0, Handler.getMain(), null, null).toBundle() + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 26f46cd073f1..63b78b687ae2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -28,8 +28,7 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NewTasksAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME -import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME +import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.ComponentNameMatcher import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER @@ -105,7 +104,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { } /** - * Check that the [ComponentMatcher.LAUNCHER] window is never visible when performing task + * Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task * transitions. * A solid color background should be shown above it. */ @@ -118,7 +117,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { } /** - * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible when performing task + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task * transitions. * A solid color background should be shown above it. */ @@ -246,8 +245,8 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { companion object { private val LAUNCH_NEW_TASK_ACTIVITY = - LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent() - private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent() + private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? { val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 16ad6309af40..9d27079c6a86 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -90,7 +90,7 @@ open class SeamlessAppRotationTest( testApp.launchViaIntent( wmHelper, stringExtras = mapOf( - ActivityOptions.EXTRA_STARVE_UI_THREAD + ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to testSpec.starveUiThread.toString() ) ) @@ -164,20 +164,23 @@ open class SeamlessAppRotationTest( /** {@inheritDoc} */ @Test @Ignore("Not applicable to this CUJ. App is full screen") - override fun statusBarLayerPositionAtStartAndEnd() { } + override fun statusBarLayerPositionAtStartAndEnd() { + } /** {@inheritDoc} */ @Test @Ignore("Not applicable to this CUJ. App is full screen") - override fun statusBarLayerIsVisibleAtStartAndEnd() { } + override fun statusBarLayerIsVisibleAtStartAndEnd() { + } /** {@inheritDoc} */ @Test @Ignore("Not applicable to this CUJ. App is full screen") - override fun statusBarWindowIsAlwaysVisible() { } + override fun statusBarWindowIsAlwaysVisible() { + } /** - * Checks that the [ComponentMatcher.STATUS_BAR] window is invisible during the whole + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is invisible during the whole * transition */ @Presubmit @@ -189,7 +192,7 @@ open class SeamlessAppRotationTest( } /** - * Checks that the [ComponentMatcher.STATUS_BAR] layer is invisible during the whole + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is invisible during the whole * transition */ @Presubmit @@ -218,14 +221,17 @@ open class SeamlessAppRotationTest( companion object { private val FlickerTestParameter.starveUiThread - get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean + get() = config.getOrDefault( + ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false) as Boolean private fun createConfig( sourceConfig: FlickerTestParameter, starveUiThread: Boolean ): FlickerTestParameter { val newConfig = sourceConfig.config.toMutableMap() - .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread } + .also { + it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread + } val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else "" return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt") } @@ -233,8 +239,8 @@ open class SeamlessAppRotationTest( /** * Creates the test configurations for seamless rotation based on the default rotation * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an - * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app - * should starve the UI thread of not + * additional flag ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate + * if the app should starve the UI thread of not */ @JvmStatic private fun getConfigurations(): List<FlickerTestParameter> { diff --git a/tests/FlickerTests/test-apps/Android.bp b/tests/FlickerTests/test-apps/Android.bp deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/tests/FlickerTests/test-apps/Android.bp +++ /dev/null diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 725963bc7ca3..6e935d11411e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -15,41 +15,41 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.server.wm.flicker.testapp"> + package="com.android.server.wm.flicker.testapp"> <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> + android:targetSdkVersion="29"/> <application android:allowBackup="false" - android:supportsRtl="true"> + android:supportsRtl="true"> <uses-library android:name="androidx.window.extensions" android:required="false"/> <activity android:name=".SimpleActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" - android:theme="@style/CutoutShortEdges" - android:label="SimpleApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" + android:theme="@style/CutoutShortEdges" + android:label="SimpleActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ImeActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" - android:theme="@style/CutoutShortEdges" - android:label="ImeApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" + android:theme="@style/CutoutShortEdges" + android:label="ImeActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ImeActivityAutoFocus" - android:theme="@style/CutoutShortEdges" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" - android:windowSoftInputMode="stateVisible" - android:configChanges="orientation|screenSize" - android:label="ImeAppAutoFocus" - android:exported="true"> + android:theme="@style/CutoutShortEdges" + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" + android:windowSoftInputMode="stateVisible" + android:configChanges="orientation|screenSize" + android:label="ImeAppAutoFocus" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -66,34 +66,34 @@ </intent-filter> </activity> <activity android:name=".SeamlessRotationActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="SeamlessApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="SeamlessActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".NonResizeableActivity" - android:theme="@style/CutoutShortEdges" - android:resizeableActivity="false" - android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" - android:label="NonResizeableApp" - android:exported="true" - android:showOnLockScreen="true"> + android:theme="@style/CutoutShortEdges" + android:resizeableActivity="false" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" + android:label="NonResizeableActivity" + android:exported="true" + android:showOnLockScreen="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> - <activity android:name=".ButtonActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="ButtonActivity" - android:exported="true"> + <activity android:name=".LaunchNewActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="LaunchNewActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -111,55 +111,56 @@ </intent-filter> </activity> <activity android:name=".DialogThemedActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" - android:configChanges="orientation|screenSize" - android:theme="@style/DialogTheme" - android:label="DialogThemedActivity" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/DialogTheme" + android:label="DialogThemedActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".PortraitOnlyActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" - android:theme="@style/CutoutShortEdges" - android:screenOrientation="portrait" - android:configChanges="orientation|screenSize" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" + android:theme="@style/CutoutShortEdges" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ImeEditorPopupDialogActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" - android:configChanges="orientation|screenSize" - android:theme="@style/CutoutShortEdges" - android:label="ImeEditorPopupDialogActivity" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/CutoutShortEdges" + android:label="ImeEditorPopupDialogActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".ShowWhenLockedActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="ShowWhenLockedActivity" - android:showWhenLocked="true" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="ShowWhenLockedActivity" + android:showWhenLocked="true" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".NotificationActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="NotificationActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -173,8 +174,8 @@ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="true"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity @@ -193,42 +194,110 @@ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity android:name=".MailActivity" - android:exported="true" - android:label="MailApp" - android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity" - android:theme="@style/Theme.AppCompat.Light"> + android:exported="true" + android:label="MailActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity" + android:theme="@style/Theme.AppCompat.Light"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".GameActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" - android:immersive="true" - android:theme="@android:style/Theme.NoTitleBar" - android:configChanges="screenSize" - android:label="GameApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" + android:immersive="true" + android:theme="@android:style/Theme.NoTitleBar" + android:configChanges="screenSize" + android:label="GameActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".PipActivity" + android:resizeableActivity="true" + android:supportsPictureInPicture="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity" + android:theme="@style/CutoutShortEdges" + android:launchMode="singleTop" + android:label="PipActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".SplitScreenActivity" + android:resizeableActivity="true" + android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" + android:theme="@style/CutoutShortEdges" + android:label="SplitScreenPrimaryActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".SplitScreenSecondaryActivity" + android:resizeableActivity="true" + android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" + android:theme="@style/CutoutShortEdges" + android:label="SplitScreenSecondaryActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".SendNotificationActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity" + android:theme="@style/CutoutShortEdges" + android:label="SendNotificationActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".LaunchBubbleActivity" + android:label="LaunchBubbleActivity" + android:exported="true" + android:theme="@style/CutoutShortEdges" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <action android:name="android.intent.action.VIEW"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".BubbleActivity" + android:label="BubbleActivity" + android:exported="false" + android:theme="@style/CutoutShortEdges" + android:resizeableActivity="true"/> <service android:name=".AssistantInteractionSessionService" android:exported="true" - android:permission="android.permission.BIND_VOICE_INTERACTION" /> + android:permission="android.permission.BIND_VOICE_INTERACTION"/> <service android:name=".AssistantRecognitionService" android:exported="true" android:label="Test Voice Interaction Service"> <intent-filter> - <action android:name="android.speech.RecognitionService" /> - <category android:name="android.intent.category.DEFAULT" /> + <action android:name="android.speech.RecognitionService"/> + <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <meta-data android:name="android.speech" - android:resource="@xml/recognition_service" /> + android:resource="@xml/recognition_service"/> </service> <service android:name=".AssistantInteractionService" @@ -236,12 +305,12 @@ android:label="Test Voice Interaction Service" android:permission="android.permission.BIND_VOICE_INTERACTION"> <intent-filter> - <action android:name="android.service.voice.VoiceInteractionService" /> + <action android:name="android.service.voice.VoiceInteractionService"/> </intent-filter> <meta-data android:name="android.voice_interaction" - android:resource="@xml/interaction_service" /> + android:resource="@xml/interaction_service"/> </service> </application> - <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png Binary files differindex d424a17b4157..d424a17b4157 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml index b43f31da748d..4ea156de9f40 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml index 0e8c7a0fe64a..45ed98c6e503 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml index f8b0ca3da26e..7c7b2cacaefb 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml index fe7bced690f9..fe7bced690f9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml index f23c46455c63..553c7fe08096 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml index 229098313afa..f7ba45b25d48 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - 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. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml index 642a08b5bbe0..79e88e49b99e 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - 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. ---> + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml index 23b51cc06f04..ed9feaf1eade 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml @@ -14,21 +14,19 @@ ~ 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="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_blue_light"> -<resources> - <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> - <item name="android:windowBackground">@android:color/darker_gray</item> - </style> + <TextView + android:id="@+id/SplitScreenTest" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center_vertical|center_horizontal" + android:text="SecondaryActivity" + android:textAppearance="?android:attr/textAppearanceLarge"/> - <style name="CutoutDefault" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">default</item> - </style> - - <style name="CutoutShortEdges" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> - - <style name="CutoutNever" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">never</item> - </style> -</resources>
\ No newline at end of file +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java index 166e3ca2a156..04a590daf32f 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java @@ -16,9 +16,6 @@ package com.android.server.wm.flicker.testapp; -import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME; -import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME; - import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -39,8 +36,6 @@ public class ActivityEmbeddingMainActivity extends Activity { private static final String TAG = "ActivityEmbeddingMainActivity"; private static final float DEFAULT_SPLIT_RATIO = 0.5f; - private ActivityEmbeddingComponent mEmbeddingComponent; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -51,20 +46,24 @@ public class ActivityEmbeddingMainActivity extends Activity { /** R.id.launch_placeholder_split_button onClick */ public void launchPlaceholderSplit(View view) { - startActivity(new Intent().setComponent( - ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME)); + startActivity( + new Intent().setComponent( + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT + ) + ); } private void initializeSplitRules() { - mEmbeddingComponent = ActivityEmbeddingAppHelper.getActivityEmbeddingComponent(); - if (mEmbeddingComponent == null) { + ActivityEmbeddingComponent embeddingComponent = + ActivityEmbeddingAppHelper.getActivityEmbeddingComponent(); + if (embeddingComponent == null) { // Embedding not supported Log.d(TAG, "ActivityEmbedding is not supported on this device"); finish(); return; } - mEmbeddingComponent.setEmbeddingRules(getSplitRules()); + embeddingComponent.setEmbeddingRules(getSplitRules()); } private Set<EmbeddingRule> getSplitRules() { @@ -72,10 +71,10 @@ public class ActivityEmbeddingMainActivity extends Activity { final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder( new Intent().setComponent( - ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME), + ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT), activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity, intent -> intent.getComponent().equals( - ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME), + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT), windowMetrics -> true) .setSplitRatio(DEFAULT_SPLIT_RATIO) .build(); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index cae3df4f9486..dcd85503ee14 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -19,94 +19,175 @@ package com.android.server.wm.flicker.testapp; import android.content.ComponentName; public class ActivityOptions { - public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread"; public static final String FLICKER_APP_PACKAGE = "com.android.server.wm.flicker.testapp"; - public static final String SEAMLESS_ACTIVITY_LAUNCHER_NAME = "SeamlessApp"; - public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".SeamlessRotationActivity"); + public static class SimpleActivity { + public static final String LABEL = "SimpleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SimpleActivity"); + } - public static final String IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME = "ImeAppAutoFocus"; - public static final ComponentName IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus"); + public static class SeamlessRotation { + public static final String LABEL = "SeamlessRotationActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SeamlessRotationActivity"); + + public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread"; + } - public static final String IME_ACTIVITY_LAUNCHER_NAME = "ImeActivity"; - public static final ComponentName IME_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class Ime { + public static class Default { + public static final String LABEL = "ImeActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeActivity"); + } + + public static class AutoFocusActivity { + public static final String LABEL = "ImeAppAutoFocus"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus"); + } - public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity"; - public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class StateInitializeActivity { + public static final String LABEL = "ImeStateInitializeActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity"); + } - public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp"; - public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".SimpleActivity"); - - public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp"; - public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".NonResizeableActivity"); - - public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp"; - public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ButtonActivity"); - - public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp"; - public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); - - public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity"; - public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".DialogThemedActivity"); - - public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity"; - public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); - - public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME = - "ImeEditorPopupDialogActivity"; - public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class EditorPopupDialogActivity { + public static final String LABEL = "ImeEditorPopupDialogActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity"); - - public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp"; - public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity"); - - public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp"; - public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".NotificationActivity"); - - public static final String ACTIVITY_EMBEDDING_LAUNCHER_NAME = "ActivityEmbeddingMainActivity"; - public static final ComponentName ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + } + } + + public static class NonResizeableActivity { + public static final String LABEL = "NonResizeableActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableActivity"); + } + + public static class DialogThemedActivity { + public static final String LABEL = "DialogThemedActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".DialogThemedActivity"); + } + + public static class PortraitOnlyActivity { + public static final String LABEL = "PortraitOnlyActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); + public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation"; + } + + public static class ActivityEmbedding { + public static class MainActivity { + public static final String LABEL = "ActivityEmbeddingMainActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity"); - public static final ComponentName - ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME = new ComponentName( - FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity"); - public static final ComponentName - ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME = new ComponentName( - FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity"); - - public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity"; - public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName( - FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity"); - - public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp"; - public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".GameActivity"); + } + + public static class PlaceholderPrimaryActivity { + public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity"); + } + + public static class PlaceholderSecondaryActivity { + public static final String LABEL = "ActivityEmbeddingPlaceholderSecondaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity"); + } + } + + public static class Notification { + public static final String LABEL = "NotificationActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NotificationActivity"); + } + + public static class Mail { + public static final String LABEL = "MailActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".MailActivity"); + } + + public static class ShowWhenLockedActivity { + public static final String LABEL = "ShowWhenLockedActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity"); + } + + public static class LaunchNewTask { + public static final String LABEL = "LaunchNewTaskActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); + } + + public static class Game { + public static final String LABEL = "GameActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".GameActivity"); + } + + public static class LaunchNewActivity { + public static final String LABEL = "LaunchNewActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewActivity"); + } + + public static class Pip { + // Test App > Pip Activity + public static final String LABEL = "PipActivity"; + public static final String MENU_ACTION_NO_OP = "No-Op"; + public static final String MENU_ACTION_ON = "On"; + public static final String MENU_ACTION_OFF = "Off"; + public static final String MENU_ACTION_CLEAR = "Clear"; + + // Intent action that this activity dynamically registers to enter picture-in-picture + public static final String ACTION_ENTER_PIP = + FLICKER_APP_PACKAGE + ".PipActivity.ENTER_PIP"; + // Intent action that this activity dynamically registers to set requested orientation. + // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra. + public static final String ACTION_SET_REQUESTED_ORIENTATION = + FLICKER_APP_PACKAGE + ".PipActivity.SET_REQUESTED_ORIENTATION"; + + // Calls enterPictureInPicture() on creation + public static final String EXTRA_ENTER_PIP = "enter_pip"; + // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation} + public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation"; + // Adds a click listener to finish this activity when it is clicked + public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish"; + + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PipActivity"); + } + + public static class SplitScreen { + public static class Primary { + public static final String LABEL = "SplitScreenPrimaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SplitScreenActivity"); + } + + public static class Secondary { + public static final String LABEL = "SplitScreenSecondaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SplitScreenSecondaryActivity"); + } + } + + public static class Bubbles { + public static class LaunchBubble { + public static final String LABEL = "LaunchBubbleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchBubbleActivity"); + } + + public static class BubbleActivity { + public static final String LABEL = "BubbleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".BubbleActivity"); + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java index bc3bc75ab903..58d7e670e908 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import android.app.Activity; diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java index 6cd93eff2803..c92b82b896f2 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import android.app.Notification; @@ -86,7 +86,7 @@ public class BubbleHelper { } - private int getNextNotifyId() { + private int getNextNotifyId() { int id = mNextNotifyId; mNextNotifyId++; return id; diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java index d4ae6c1313bf..722929fb81e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; -import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION; +import static com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION; import android.os.Bundle; diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java index 71fa66d8a61c..dea34442464d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import android.app.Activity; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java index b42ac2a6fd97..e5710c850f07 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java @@ -22,7 +22,7 @@ import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; -public class ButtonActivity extends Activity { +public class LaunchNewActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -30,11 +30,11 @@ public class ButtonActivity extends Activity { p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(p); - setContentView(R.layout.activity_button); + setContentView(R.layout.activity_launch_new); Button button = findViewById(R.id.launch_second_activity); button.setOnClickListener(v -> { - Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class); + Intent intent = new Intent(LaunchNewActivity.this, SimpleActivity.class); startActivity(intent); }); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index b31af385d363..a4dd5753539d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -50,12 +50,12 @@ public class NotificationActivity extends Activity { Intent resultIntent = new Intent(this, NotificationActivity.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addNextIntentWithParentStack(resultIntent); - PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent(0, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - + PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, + new Intent(this, NotificationActivity.class), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) + .setWhen(System.currentTimeMillis()) .setContentTitle("Flicker Test Notification") .setContentText("Flicker Test Notification") // Set the intent that will fire when the user taps the notification diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java index 615b1730579c..cdb1d42bd4f2 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import static android.media.MediaMetadata.METADATA_KEY_TITLE; import static android.media.session.PlaybackState.ACTION_PAUSE; @@ -24,10 +24,10 @@ import static android.media.session.PlaybackState.STATE_PAUSED; import static android.media.session.PlaybackState.STATE_PLAYING; import static android.media.session.PlaybackState.STATE_STOPPED; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP; -import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_SET_REQUESTED_ORIENTATION; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_ENTER_PIP; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_PIP_ORIENTATION; import android.app.Activity; import android.app.PendingIntent; @@ -127,7 +127,6 @@ public class PipActivity extends Activity { break; default: Log.w(TAG, "Unhandled action=" + intent.getAction()); - return; } } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java index 5cf81cb90fbc..ce7a0059fa2d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.testapp; import static android.os.SystemClock.sleep; -import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD; +import static com.android.server.wm.flicker.testapp.ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD; import android.app.Activity; import android.os.Bundle; diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java index 9c82eea1e8b8..70196aee9ff1 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java index baa1e6fdd1e9..a8ce8ff8f589 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.testapp; +package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 54b3c400af4f..f924b2e9b932 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; @@ -276,7 +277,6 @@ public class VcnManagementServiceTest { @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); verify(mSubscriptionTracker).register(); @@ -494,8 +494,10 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); - mTestLooper.dispatchAll(); + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); verify(mMockPolicyListener).onPolicyChanged(); } @@ -521,6 +523,92 @@ public class VcnManagementServiceTest { assertEquals(0, mVcnMgmtSvc.getAllVcns().size()); } + /** + * Tests an intermediate state where carrier privileges are marked as lost before active data + * subId changes during a SIM ejection. + * + * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to + * immediately. + */ + @Test + public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges() + throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate privileges lost + triggerSubscriptionTrackerCbAndGetSnapshot( + TEST_SUBSCRIPTION_ID, + TEST_UUID_2, + Collections.emptySet(), + Collections.emptyMap(), + false /* hasCarrierPrivileges */); + + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() + throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCbAndGetSnapshot( + INVALID_SUBSCRIPTION_ID, + null /* activeDataSubscriptionGroup */, + Collections.emptySet(), + Collections.emptyMap(), + false /* hasCarrierPrivileges */); + + // Simulate new SIM loaded right during teardown delay. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); + + // Verify that even after the full timeout duration, the VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn, never()).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); + + // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new + // vcnInstance. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); + final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Verify that new instance was different, and the old one was torn down + assertTrue(oldInstance != newInstance); + verify(oldInstance).teardownAsynchronously(); + + // Verify that even after the full timeout duration, the new VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(newInstance, never()).teardownAsynchronously(); + } + @Test public void testPackageChangeListenerRegistered() throws Exception { verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> { @@ -910,8 +998,6 @@ public class VcnManagementServiceTest { private void setupSubscriptionAndStartVcn( int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); - triggerSubscriptionTrackerCbAndGetSnapshot( subGrp, Collections.singleton(subGrp), @@ -1007,7 +1093,6 @@ public class VcnManagementServiceTest { private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); final ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class); @@ -1252,14 +1337,15 @@ public class VcnManagementServiceTest { true /* isActive */, true /* hasCarrierPrivileges */); - // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes - // inactive. + // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown + // timeout so the VCN goes inactive. final TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, Collections.singleton(TEST_UUID_1), Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), false /* hasCarrierPrivileges */); + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index cf58f989a7d4..aeedf8b5805e 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -395,6 +395,12 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { << output_format_.value()); return 1; } + if (enable_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled; + } + if (force_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Forced; + } return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_, xml_flattener_options_); diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 2cdb0c87310f..6c096496a1ea 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -34,10 +34,18 @@ class ConvertCommand : public Command { AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. " "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &table_flattener_options_.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &enable_sparse_encoding_); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &force_sparse_encoding_); AddOptionalSwitch("--keep-raw-values", android::base::StringPrintf("Preserve raw attribute values in xml files when using the" " '%s' output format", kOutputFormatBinary), @@ -56,6 +64,8 @@ class ConvertCommand : public Command { std::string output_path_; std::optional<std::string> output_format_; bool verbose_ = false; + bool enable_sparse_encoding_ = false; + bool force_sparse_encoding_ = false; }; int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer, diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 74a8bbd4fd0d..116dcd641bc1 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2419,6 +2419,9 @@ int LinkCommand::Action(const std::vector<std::string>& args) { << "the --merge-only flag can be only used when building a static library"); return 1; } + if (options_.use_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } // The default build type. context.SetPackageType(PackageType::kApp); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index a5623cb1f1e1..6c8d1434fb88 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -69,6 +69,7 @@ struct LinkOptions { bool no_resource_removal = false; bool no_xml_namespaces = false; bool do_not_compress_anything = false; + bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; @@ -156,8 +157,8 @@ class LinkCommand : public Command { "defaults. Use this only when building runtime resource overlay packages.", &options_.no_resource_removal); AddOptionalSwitch("--enable-sparse-encoding", - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.", + &options_.use_sparse_encoding); AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.", &legacy_x_flag_); AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.", diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 4033983f4c0f..e37c2d450b73 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -427,6 +427,13 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { return 1; } + if (options_.enable_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } + if (options_.force_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced; + } + if (target_densities_) { // Parse the target screen densities. for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index ff63e8dd76d4..10b84b0c6bda 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -61,6 +61,12 @@ struct OptimizeOptions { // Path to the output map of original resource paths to shortened paths. std::optional<std::string> shortened_paths_map_path; + + // Whether sparse encoding should be used for O+ resources. + bool enable_sparse_encoding = false; + + // Whether sparse encoding should be used for all resources. + bool force_sparse_encoding = false; }; class OptimizeCommand : public Command { @@ -96,10 +102,18 @@ class OptimizeCommand : public Command { "Comma separated list of artifacts to keep. If none are specified,\n" "all artifacts will be kept.", &kept_artifacts_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &options_.enable_sparse_encoding); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &options_.force_sparse_encoding); AddOptionalSwitch("--collapse-resource-names", "Collapses resource names to a single value in the key string pool. Resources can \n" "be exempted using the \"no_collapse\" directive in a file specified by " diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 4fb7ed19ed20..22f278cc0b22 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -231,14 +231,14 @@ struct OverlayableChunk { class PackageFlattener { public: PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package, - const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, - bool collapse_key_stringpool, + const std::map<size_t, std::string>* shared_libs, + SparseEntriesMode sparse_entries, bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), - use_sparse_entries_(use_sparse_entries), + sparse_entries_(sparse_entries), collapse_key_stringpool_(collapse_key_stringpool), name_collapse_exemptions_(name_collapse_exemptions) { } @@ -367,10 +367,12 @@ class PackageFlattener { } } - bool sparse_encode = use_sparse_entries_; + bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled || + sparse_entries_ == SparseEntriesMode::Forced; - if (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0) { - // Sparse encode if sdk version is not set in context and config. + if (sparse_entries_ == SparseEntriesMode::Forced || + (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) { + // Sparse encode if forced or sdk version is not set in context and config. } else { // Otherwise, only sparse encode if the entries will be read on platforms S_V2+. sparse_encode = sparse_encode && @@ -712,7 +714,7 @@ class PackageFlattener { android::IDiagnostics* diag_; const ResourceTablePackageView package_; const std::map<size_t, std::string>* shared_libs_; - bool use_sparse_entries_; + SparseEntriesMode sparse_entries_; android::StringPool type_pool_; android::StringPool key_pool_; bool collapse_key_stringpool_; @@ -768,7 +770,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { } PackageFlattener flattener(context, package, &table->included_packages_, - options_.use_sparse_entries, options_.collapse_key_stringpool, + options_.sparse_entries, options_.collapse_key_stringpool, options_.name_collapse_exemptions); if (!flattener.FlattenPackage(&package_buffer)) { return false; diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 1eec0e4d8c51..c6d30335ed53 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -29,12 +29,20 @@ namespace aapt { // preferred. constexpr const size_t kSparseEncodingThreshold = 60; +enum class SparseEntriesMode { + // Disables sparse encoding for entries. + Disabled, + // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less + // than O only applies sparse encoding for resource configuration available on O+. + Enabled, + // Enables sparse encoding for all entries regardless of minSdk. + Forced, +}; + struct TableFlattenerOptions { - // When true, types for configurations with a sparse set of entries are encoded + // When enabled, types for configurations with a sparse set of entries are encoded // as a sparse map of entry ID and offset to actual data. - // This is only available on platforms O+ and will only be respected when - // minSdk is O+. - bool use_sparse_entries = false; + SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled; // When true, the key string pool in the final ResTable // is collapsed to a single entry. All resource entries diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index f551bf61dc06..b69daddf1c7a 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -337,7 +337,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -380,7 +380,29 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; + + std::string no_sparse_contents; + ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); + + std::string sparse_contents; + ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); + + EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); +} + +TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .SetCompilationPackage("android") + .SetPackageId(0x01) + .SetMinSdkVersion(SDK_LOLLIPOP) + .Build(); + + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); + auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); + + TableFlattenerOptions options; + options.sparse_entries = SparseEntriesMode::Forced; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -399,7 +421,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -442,7 +464,7 @@ TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp index 985874b7179f..fe97a903bff8 100644 --- a/tools/processors/immutability/Android.bp +++ b/tools/processors/immutability/Android.bp @@ -14,24 +14,26 @@ java_library_host { "src/**/*.java", ], use_tools_jar: true, - // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java - // language level 8 (see b/139342589): - java_version: "1.8", - openjdk9: { - javacflags: [ - "--add-modules=jdk.compiler", - "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - ], - }, + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], } java_plugin { name: "ImmutabilityAnnotationProcessor", processor_class: "android.processor.immutability.ImmutabilityProcessor", static_libs: ["ImmutabilityAnnotationProcessorHostLibrary"], + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], } java_library { @@ -58,6 +60,13 @@ java_test_host { java_resources: [":ImmutabilityAnnotationJavaSource"], test_suites: ["general-tests"], + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], } filegroup { diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt index 43fc3a7ccef2..3ab09a8366be 100644 --- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt +++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + package android.processor.immutability import com.sun.tools.javac.code.Symbol @@ -206,8 +208,8 @@ class ImmutabilityProcessor : AbstractProcessor() { } } - if (isRegularClass && !anyError && allowFinalClassesFinalFields - && !classType.modifiers.contains(Modifier.FINAL) + if (isRegularClass && !anyError && allowFinalClassesFinalFields && + !classType.modifiers.contains(Modifier.FINAL) ) { printError(parentChain, elementToPrint, MessageUtils.classNotFinalFailure(className)) return true @@ -367,4 +369,4 @@ class ImmutabilityProcessor : AbstractProcessor() { private fun isIgnored(symbol: Symbol) = symbol.getAnnotation(Immutable.Ignore::class.java) != null -}
\ No newline at end of file +} |