Merge "Support gainmap HW copy." into udc-dev
diff --git a/Android.bp b/Android.bp
index effd7ce..d961599 100644
--- a/Android.bp
+++ b/Android.bp
@@ -580,7 +580,7 @@
"--hide Todo " +
"--hide Typo " +
"--hide UnavailableSymbol " +
- "--manifest $(location core/res/AndroidManifest.xml) "
+ "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
packages_to_document = [
"android",
@@ -617,7 +617,7 @@
sdk_version: "none",
system_modules: "none",
java_version: "1.8",
- arg_files: ["core/res/AndroidManifest.xml"],
+ arg_files: [":frameworks-base-core-AndroidManifest.xml"],
aidl: {
local_include_dirs: [
"media/aidl",
@@ -696,12 +696,3 @@
"ProtoLibraries.bp",
"TestProtoLibraries.bp",
]
-
-java_api_contribution {
- name: "api-stubs-docs-non-updatable-public-stubs",
- api_surface: "public",
- api_file: "core/api/current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 38413c2..b005591 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -36,8 +36,8 @@
args: metalava_framework_docs_args,
check_api: {
current: {
- api_file: "core/api/current.txt",
- removed_api_file: "core/api/removed.txt",
+ api_file: ":non-updatable-current.txt",
+ removed_api_file: ":non-updatable-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.public.latest",
@@ -88,8 +88,8 @@
args: metalava_framework_docs_args + priv_apps,
check_api: {
current: {
- api_file: "core/api/system-current.txt",
- removed_api_file: "core/api/system-removed.txt",
+ api_file: ":non-updatable-system-current.txt",
+ removed_api_file: ":non-updatable-system-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.system.latest",
@@ -99,7 +99,7 @@
api_lint: {
enabled: true,
new_since: ":android.api.system.latest",
- baseline_file: "core/api/system-lint-baseline.txt",
+ baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
dists: [
@@ -127,12 +127,12 @@
args: metalava_framework_docs_args + test + priv_apps_in_stubs,
check_api: {
current: {
- api_file: "core/api/test-current.txt",
- removed_api_file: "core/api/test-removed.txt",
+ api_file: ":non-updatable-test-current.txt",
+ removed_api_file: ":non-updatable-test-removed.txt",
},
api_lint: {
enabled: true,
- baseline_file: "core/api/test-lint-baseline.txt",
+ baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
dists: [
@@ -172,8 +172,8 @@
args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
check_api: {
current: {
- api_file: "core/api/module-lib-current.txt",
- removed_api_file: "core/api/module-lib-removed.txt",
+ api_file: ":non-updatable-module-lib-current.txt",
+ removed_api_file: ":non-updatable-module-lib-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.module-lib.latest",
@@ -183,7 +183,7 @@
api_lint: {
enabled: true,
new_since: ":android.api.module-lib.latest",
- baseline_file: "core/api/module-lib-lint-baseline.txt",
+ baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
dists: [
@@ -364,15 +364,15 @@
java_library {
name: "android_system_server_stubs_current",
- defaults: ["android_stubs_dists_default"],
+ defaults: [
+ "android.jar_defaults",
+ "android_stubs_dists_default",
+ ],
srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
"android_module_lib_stubs_current",
],
- sdk_version: "none",
- system_modules: "none",
- java_version: "1.8",
dist: {
dir: "apistubs/android/system-server",
},
@@ -575,20 +575,7 @@
droidstubs {
name: "hwbinder-stubs-docs",
- srcs: [
- "core/java/android/os/HidlSupport.java",
- "core/java/android/os/HidlMemory.java",
- "core/java/android/os/HwBinder.java",
- "core/java/android/os/HwBlob.java",
- "core/java/android/os/HwParcel.java",
- "core/java/android/os/IHwBinder.java",
- "core/java/android/os/IHwInterface.java",
- "core/java/android/os/DeadObjectException.java",
- "core/java/android/os/DeadSystemException.java",
- "core/java/android/os/NativeHandle.java",
- "core/java/android/os/RemoteException.java",
- "core/java/android/util/AndroidException.java",
- ],
+ srcs: [":hwbinder-stubs-srcs"],
libs: ["framework-annotations-lib"],
installable: false,
sdk_version: "core_platform",
@@ -610,12 +597,3 @@
],
visibility: ["//visibility:public"],
}
-
-java_api_contribution {
- name: "frameworks-base-core-api-module-lib-stubs",
- api_surface: "module-lib",
- api_file: "core/api/module-lib-current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
index 2e44d82..e9c6c1a 100644
--- a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
+++ b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
@@ -76,6 +76,7 @@
implements ManualBenchmarkState.CustomizedIterationListener {
private static final String TAG = ImePerfTest.class.getSimpleName();
private static final long ANIMATION_NOT_STARTED = -1;
+ private static final int WAIT_PROCESS_KILL_TIMEOUT_MS = 2000;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@@ -248,19 +249,18 @@
boolean shouldRetry = false;
while (shouldRetry || state.keepRunning(measuredTimeNs)) {
shouldRetry = false;
- killBaselineIme();
+ killBaselineImeSync();
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
getInstrumentation().getContext()))) {
+ if (!mIsTraceStarted) {
+ startAsyncAtrace();
+ }
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
final Activity activity = getActivityWithFocus();
setImeListener(activity, latchStart, null /* latchEnd */);
latchStart.set(new CountDownLatch(1));
- if (!mIsTraceStarted) {
- startAsyncAtrace();
- }
-
final WindowInsetsController controller =
activity.getWindow().getDecorView().getWindowInsetsController();
AtomicLong startTime = new AtomicLong();
@@ -270,6 +270,7 @@
});
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
+ stopAsyncAtraceAndDumpTraces();
if (measuredTimeNs == ANIMATION_NOT_STARTED) {
// Animation didn't start within timeout,
@@ -285,7 +286,7 @@
addResultToState(state);
}
- private void killBaselineIme() {
+ private void killBaselineImeSync() {
// pidof returns a space separated list of numeric PIDs.
String result = SystemUtil.runShellCommand(
"pidof com.android.perftests.inputmethod:BaselineIME");
@@ -294,7 +295,13 @@
if (TextUtils.isEmpty(pid)) {
continue;
}
- Process.killProcess(Integer.parseInt(pid));
+ final int pidToKill = Integer.parseInt(pid);
+ Process.killProcess(pidToKill);
+ try {
+ // Wait kill IME process being settled down.
+ Process.waitForProcessDeath(pidToKill, WAIT_PROCESS_KILL_TIMEOUT_MS);
+ } catch (Exception e) {
+ }
}
}
@@ -381,7 +388,7 @@
}
} finally {
if (mIsTraceStarted) {
- stopAsyncAtrace();
+ stopAsyncAtraceAndDumpTraces();
}
}
mActivityRule.finishActivity();
@@ -488,7 +495,7 @@
startAsyncAtrace("wm view");
}
- private void stopAsyncAtrace() {
+ private void stopAsyncAtraceAndDumpTraces() {
if (!mIsTraceStarted) {
return;
}
@@ -504,6 +511,14 @@
}
}
+ private void stopAsyncAtrace() {
+ if (!mIsTraceStarted) {
+ return;
+ }
+ mIsTraceStarted = false;
+ getUiAutomation().executeShellCommand("atrace --async_stop");
+ }
+
@Override
public void onStart(int iteration) {
// Do not capture trace when profiling because the result will be much slower.
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
index ca59137..804baf4 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
@@ -73,7 +73,7 @@
}
public static void startAsyncAtrace(String tags) {
- getUiAutomation().executeShellCommand("atrace -b 32768 --async_start " + tags);
+ getUiAutomation().executeShellCommand("atrace --async_start -b 32768 -c " + tags);
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
diff --git a/api/Android.bp b/api/Android.bp
index 78ddc6a..73dbd28 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -31,10 +31,12 @@
"blueprint",
"soong",
"soong-android",
+ "soong-bp2build",
"soong-genrule",
"soong-java",
],
srcs: ["api.go"],
+ testSrcs: ["api_test.go"],
pluginFor: ["soong_build"],
}
@@ -261,3 +263,15 @@
out: ["combined-removed-dex.txt"],
cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
}
+
+java_genrule {
+ name: "api_fingerprint",
+ srcs: [
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-system-current.txt",
+ ":frameworks-base-api-module-lib-current.txt",
+ ":frameworks-base-api-system-server-current.txt",
+ ],
+ out: ["api_fingerprint.txt"],
+ cmd: "cat $(in) | md5sum | cut -d' ' -f1 > $(out)",
+}
diff --git a/api/api.go b/api/api.go
index 077ab96..25d9728 100644
--- a/api/api.go
+++ b/api/api.go
@@ -20,6 +20,7 @@
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/bazel"
"android/soong/genrule"
"android/soong/java"
)
@@ -30,6 +31,7 @@
const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+
// List of modules that are not yet updatable, and hence they can still compile
// against hidden APIs. These modules are filtered out when building the
// updatable-framework-module-impl (because updatable-framework-module-impl is
@@ -59,6 +61,7 @@
type CombinedApis struct {
android.ModuleBase
+ android.BazelModuleBase
properties CombinedApisProperties
}
@@ -99,6 +102,19 @@
Visibility []string
}
+type Bazel_module struct {
+ Bp2build_available *bool
+}
+type bazelProperties struct {
+ *Bazel_module
+}
+
+var bp2buildNotAvailable = bazelProperties{
+ &Bazel_module{
+ Bp2build_available: proptools.BoolPtr(false),
+ },
+}
+
// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
type MergedTxtDefinition struct {
// "current.txt" or "removed.txt"
@@ -144,7 +160,7 @@
},
}
props.Visibility = []string{"//visibility:public"}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
@@ -174,7 +190,7 @@
props := fgProps{}
props.Name = proptools.StringPtr(i.name)
props.Srcs = createSrcs(i.modules, i.tag)
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
}
@@ -223,7 +239,7 @@
props.Tools = []string{"api_versions_trimmer"}
props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
}
@@ -315,7 +331,7 @@
props.Name = proptools.StringPtr("all-modules-public-stubs-source")
props.Srcs = createSrcs(modules, "{.public.stubs.source}")
props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
@@ -389,9 +405,57 @@
module.AddProperties(&module.properties)
android.InitAndroidModule(module)
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
+ android.InitBazelModule(module)
return module
}
+type bazelCombinedApisAttributes struct {
+ Scope bazel.StringAttribute
+ Base bazel.LabelAttribute
+ Deps bazel.LabelListAttribute
+}
+
+// combined_apis bp2build converter
+func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+ basePrefix := "non-updatable"
+ scopeNames := []string{"public", "system", "module-lib", "system-server"}
+ scopeToSuffix := map[string]string{
+ "public": "-current.txt",
+ "system": "-system-current.txt",
+ "module-lib": "-module-lib-current.txt",
+ "system-server": "-system-server-current.txt",
+ }
+
+ for _, scopeName := range scopeNames{
+ suffix := scopeToSuffix[scopeName]
+ name := a.Name() + suffix
+
+ var scope bazel.StringAttribute
+ scope.SetValue(scopeName)
+
+ var base bazel.LabelAttribute
+ base.SetValue(android.BazelLabelForModuleDepSingle(ctx, basePrefix+suffix))
+
+ var deps bazel.LabelListAttribute
+ classpath := a.properties.Bootclasspath
+ if scopeName == "system-server" {
+ classpath = a.properties.System_server_classpath
+ }
+ deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, classpath))
+
+ attrs := bazelCombinedApisAttributes{
+ Scope: scope,
+ Base: base,
+ Deps: deps,
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "merged_txts",
+ Bzl_load_location: "//build/bazel/rules/java:merged_txts.bzl",
+ }
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, &attrs)
+ }
+}
+
// Various utility methods below.
// Creates an array of ":<m><tag>" for each m in <modules>.
diff --git a/api/api_test.go b/api/api_test.go
new file mode 100644
index 0000000..15b695c
--- /dev/null
+++ b/api/api_test.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package api
+
+import (
+ "testing"
+
+ "android/soong/android"
+ "android/soong/bp2build"
+)
+
+func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
+ t.Helper()
+ (&tc).ModuleTypeUnderTest = "combined_apis"
+ (&tc).ModuleTypeUnderTestFactory = combinedApisModuleFactory
+ bp2build.RunBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) {
+ t.Helper()
+ runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
+}
+
+func TestCombinedApisGeneral(t *testing.T) {
+ runCombinedApisTestCase(t, bp2build.Bp2buildTestCase{
+ Description: "combined_apis, general case",
+ Blueprint: `combined_apis {
+ name: "foo",
+ bootclasspath: ["bcp"],
+ system_server_classpath: ["ssc"],
+}
+`,
+ ExpectedBazelTargets: []string{
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{
+ "scope": `"public"`,
+ "base": `":non-updatable-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{
+ "scope": `"system"`,
+ "base": `":non-updatable-system-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{
+ "scope": `"module-lib"`,
+ "base": `":non-updatable-module-lib-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{
+ "scope": `"system-server"`,
+ "base": `":non-updatable-system-server-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":ssc__BP2BUILD__MISSING__DEP"]`,
+ }),
+ },
+ })
+}
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 114a957..71a2ca2 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -13,7 +13,10 @@
// limitations under the License.
package {
- default_visibility: ["//visibility:private"],
+ default_visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
// 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"
@@ -27,31 +30,33 @@
filegroup {
name: "non-updatable-current.txt",
srcs: ["current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-removed.txt",
srcs: ["removed.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-current.txt",
srcs: ["system-current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-removed.txt",
srcs: ["system-removed.txt"],
- visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-lint-baseline.txt",
+ srcs: ["system-lint-baseline.txt"],
}
filegroup {
name: "non-updatable-module-lib-current.txt",
srcs: ["module-lib-current.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
@@ -61,7 +66,46 @@
name: "non-updatable-module-lib-removed.txt",
srcs: ["module-lib-removed.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
}
+
+filegroup {
+ name: "non-updatable-module-lib-lint-baseline.txt",
+ srcs: ["module-lib-lint-baseline.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-current.txt",
+ srcs: ["test-current.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-removed.txt",
+ srcs: ["test-removed.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-lint-baseline.txt",
+ srcs: ["test-lint-baseline.txt"],
+}
+
+java_api_contribution {
+ name: "api-stubs-docs-non-updatable-public-stubs",
+ api_surface: "public",
+ api_file: "current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
+java_api_contribution {
+ name: "frameworks-base-core-api-module-lib-stubs",
+ api_surface: "module-lib",
+ api_file: "module-lib-current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index ac63513..211f603 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15484,14 +15484,14 @@
method @NonNull public float getMinDisplayRatioForHdrTransition();
method @NonNull public float[] getRatioMax();
method @NonNull public float[] getRatioMin();
- method @NonNull public void setDisplayRatioForFullHdr(float);
- method @NonNull public void setEpsilonHdr(float, float, float);
- method @NonNull public void setEpsilonSdr(float, float, float);
+ method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float);
+ method public void setEpsilonHdr(float, float, float);
+ method public void setEpsilonSdr(float, float, float);
method public void setGainmapContents(@NonNull android.graphics.Bitmap);
- method @NonNull public void setGamma(float, float, float);
- method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
- method @NonNull public void setRatioMax(float, float, float);
- method @NonNull public void setRatioMin(float, float, float);
+ method public void setGamma(float, float, float);
+ method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
+ method public void setRatioMax(float, float, float);
+ method public void setRatioMin(float, float, float);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Gainmap> CREATOR;
}
@@ -27308,6 +27308,7 @@
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
field public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC";
field public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark";
field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
@@ -27436,6 +27437,7 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
+ method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -36889,6 +36891,7 @@
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
+ method @Nullable public final String getTypeAnonymous(@NonNull android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public boolean isChildDocument(String, String);
method public String moveDocument(String, String, String) throws java.io.FileNotFoundException;
@@ -40640,9 +40643,11 @@
}
public class CredentialEntry implements android.os.Parcelable {
+ ctor public CredentialEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice);
+ ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice);
method public int describeContents();
- method @NonNull public android.service.credentials.BeginGetCredentialOption getBeginGetCredentialOption();
+ method @NonNull public String getBeginGetCredentialOptionId();
method @NonNull public android.app.slice.Slice getSlice();
method @NonNull public String getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a8a48b7..11a0f0b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2363,6 +2363,8 @@
method @NonNull public String getTargetId();
method @NonNull public java.util.List<java.lang.String> getTargetIds();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_DELETE = 9; // 0x9
+ field public static final int ACTION_DISMISS = 10; // 0xa
field public static final int ACTION_DRAGNDROP = 7; // 0x7
field public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6; // 0x6
field public static final int ACTION_LAUNCH_TOUCH = 5; // 0x5
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a9ae225..a3bc392 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -899,6 +899,7 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1071,6 +1072,40 @@
}
+package android.credentials {
+
+ public final class CredentialManager {
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.QUERY_ALL_PACKAGES, "android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"}) public java.util.List<android.credentials.CredentialProviderInfo> getCredentialProviderServicesForTesting(int);
+ method public static boolean isServiceEnabled(@NonNull android.content.Context);
+ field public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; // 0x0
+ field public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; // 0x1
+ field public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; // 0x2
+ }
+
+ public final class CredentialProviderInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCapabilities();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
+ method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
+ method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+ method @NonNull public boolean hasCapability(@NonNull String);
+ method public boolean isEnabled();
+ method public boolean isSystemProvider();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialProviderInfo> CREATOR;
+ }
+
+ public static final class CredentialProviderInfo.Builder {
+ ctor public CredentialProviderInfo.Builder(@NonNull android.content.pm.ServiceInfo);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.credentials.CredentialProviderInfo build();
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean);
+ }
+
+}
+
package android.credentials.ui {
public final class AuthenticationEntry implements android.os.Parcelable {
@@ -1921,6 +1956,10 @@
method public void removeHardwareDevice(int);
}
+ public class TvView extends android.view.ViewGroup {
+ method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle);
+ }
+
}
package android.media.tv.tuner {
@@ -3303,7 +3342,9 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
@@ -3936,6 +3977,18 @@
method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
}
+ public class WindowInfosListenerForTest {
+ ctor public WindowInfosListenerForTest();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void addWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ method public void removeWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ }
+
+ public static class WindowInfosListenerForTest.WindowInfo {
+ field @NonNull public final android.graphics.Rect bounds;
+ field @NonNull public final String name;
+ field @NonNull public final android.os.IBinder windowToken;
+ }
+
public class WindowOrganizer {
ctor public WindowOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 738e2de..3969577 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -285,6 +285,28 @@
],
}
+filegroup {
+ name: "hwbinder-stubs-srcs",
+ srcs: [
+ "android/os/HidlSupport.java",
+ "android/os/HidlMemory.java",
+ "android/os/HwBinder.java",
+ "android/os/HwBlob.java",
+ "android/os/HwParcel.java",
+ "android/os/IHwBinder.java",
+ "android/os/IHwInterface.java",
+ "android/os/DeadObjectException.java",
+ "android/os/DeadSystemException.java",
+ "android/os/NativeHandle.java",
+ "android/os/RemoteException.java",
+ "android/util/AndroidException.java",
+ ],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
+
cc_defaults {
name: "incremental_default",
cflags: [
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 87fe215..b4068db 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4208,8 +4208,8 @@
* processes to reclaim memory; the system will take care of restarting
* these processes in the future as needed.
*
- * <p class="note">On devices with a {@link Build.VERSION#SECURITY_PATCH} of 2022-12-01 or
- * greater, third party applications can only use this API to kill their own processes.
+ * <p class="note">On devices that run Android 14 or higher,
+ * third party applications can only use this API to kill their own processes.
* </p>
*
* @param packageName The name of the package whose processes are to
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 5cad1ae..ac92811 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -93,7 +93,7 @@
private static final String TAG = "Instrumentation";
- private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+ private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 3957732..b6e83c8 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -19,6 +19,9 @@
import com.android.server.LocalServices;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that
* can be accessed from any place without risking dead locks.
@@ -61,6 +64,12 @@
public abstract boolean canAdminGrantSensorsPermissions();
/**
+ * Returns a list of package names for which all launcher shortcuts should be modified to be
+ * launched in the managed profile and badged accordingly.
+ */
+ public abstract List<String> getLauncherShortcutOverrides();
+
+ /**
* Empty implementation.
*/
private static class EmptyDevicePolicyCache extends DevicePolicyCache {
@@ -85,5 +94,9 @@
public boolean canAdminGrantSensorsPermissions() {
return false;
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ return new ArrayList<>();
+ }
}
}
diff --git a/core/java/android/app/search/SearchTargetEvent.java b/core/java/android/app/search/SearchTargetEvent.java
index d4915af..e8ef922 100644
--- a/core/java/android/app/search/SearchTargetEvent.java
+++ b/core/java/android/app/search/SearchTargetEvent.java
@@ -50,7 +50,9 @@
ACTION_LAUNCH_TOUCH,
ACTION_LAUNCH_KEYBOARD_FOCUS,
ACTION_DRAGNDROP,
- ACTION_SURFACE_INVISIBLE
+ ACTION_SURFACE_INVISIBLE,
+ ACTION_DELETE,
+ ACTION_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionType {}
@@ -114,6 +116,16 @@
*/
public static final int ACTION_SURFACE_INVISIBLE = 8;
+ /**
+ * Constant that defines user deleted a target.
+ */
+ public static final int ACTION_DELETE = 9;
+
+ /**
+ * Constant that defines user dismissed a target.
+ */
+ public static final int ACTION_DISMISS = 10;
+
private SearchTargetEvent(@NonNull List<String> targetIds,
@Nullable String location,
@ActionType int actionType,
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f8f2663..eb14cc4 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,34 @@
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 08cfbf7..96a42e2 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -33,6 +33,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
+import android.content.pm.LauncherActivityInfoInternal;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
@@ -114,4 +115,5 @@
String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
int userId);
+ Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage, int userId);
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 16e720e..a4d5327 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -34,7 +34,6 @@
*/
public class LauncherActivityInfo {
private final PackageManager mPm;
- private UserHandle mUser;
private final LauncherActivityInfoInternal mInternal;
/**
@@ -43,9 +42,8 @@
* @param context The context for fetching resources.
*/
- LauncherActivityInfo(Context context, UserHandle user, LauncherActivityInfoInternal internal) {
+ LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal) {
mPm = context.getPackageManager();
- mUser = user;
mInternal = internal;
}
@@ -70,7 +68,7 @@
* @return The UserHandle of the profile.
*/
public UserHandle getUser() {
- return mUser;
+ return mInternal.getUser();
}
/**
@@ -180,6 +178,6 @@
public Drawable getBadgedIcon(int density) {
Drawable originalIcon = getIcon(density);
- return mPm.getUserBadgedIcon(originalIcon, mUser);
+ return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
}
}
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 46c415d..5aac97d 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* @hide
@@ -30,23 +31,27 @@
@NonNull private ActivityInfo mActivityInfo;
@NonNull private ComponentName mComponentName;
@NonNull private IncrementalStatesInfo mIncrementalStatesInfo;
+ @NonNull private UserHandle mUser;
/**
* @param info ActivityInfo from which to create the LauncherActivityInfo.
* @param incrementalStatesInfo The package's states.
+ * @param user The user the activity info belongs to.
*/
public LauncherActivityInfoInternal(@NonNull ActivityInfo info,
- @NonNull IncrementalStatesInfo incrementalStatesInfo) {
+ @NonNull IncrementalStatesInfo incrementalStatesInfo,
+ @NonNull UserHandle user) {
mActivityInfo = info;
mComponentName = new ComponentName(info.packageName, info.name);
mIncrementalStatesInfo = incrementalStatesInfo;
+ mUser = user;
}
public LauncherActivityInfoInternal(Parcel source) {
- mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
+ mActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
- mIncrementalStatesInfo = source.readParcelable(
- IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
+ mIncrementalStatesInfo = source.readTypedObject(IncrementalStatesInfo.CREATOR);
+ mUser = source.readTypedObject(UserHandle.CREATOR);
}
public ComponentName getComponentName() {
@@ -57,6 +62,10 @@
return mActivityInfo;
}
+ public UserHandle getUser() {
+ return mUser;
+ }
+
public IncrementalStatesInfo getIncrementalStatesInfo() {
return mIncrementalStatesInfo;
}
@@ -68,8 +77,9 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mActivityInfo, 0);
- dest.writeParcelable(mIncrementalStatesInfo, 0);
+ dest.writeTypedObject(mActivityInfo, flags);
+ dest.writeTypedObject(mIncrementalStatesInfo, flags);
+ dest.writeTypedObject(mUser, flags);
}
public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index f8c4974..8989006 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -64,6 +64,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
@@ -793,13 +794,45 @@
if (ai == null) {
return null;
}
- return new LauncherActivityInfo(mContext, user, ai);
+ return new LauncherActivityInfo(mContext, ai);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
+ * Returns overrides for the activities that should be launched for the shortcuts of certain
+ * package names.
+ *
+ * @return {@link Map} whose keys are package names and whose values are the
+ * {@link LauncherActivityInfo}s that should be used for those packages' shortcuts. If there are
+ * no activity overrides, an empty {@link Map} will be returned.
+ *
+ * @hide
+ */
+ @NonNull
+ public Map<String, LauncherActivityInfo> getActivityOverrides() {
+ Map<String, LauncherActivityInfo> activityOverrides = new ArrayMap<>();
+ try {
+ Map<String, LauncherActivityInfoInternal> activityOverridesInternal =
+ mService.getActivityOverrides(mContext.getPackageName(), mContext.getUserId());
+ for (Map.Entry<String, LauncherActivityInfoInternal> packageToOverride :
+ activityOverridesInternal.entrySet()) {
+ activityOverrides.put(
+ packageToOverride.getKey(),
+ new LauncherActivityInfo(
+ mContext,
+ packageToOverride.getValue()
+ )
+ );
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return activityOverrides;
+ }
+
+ /**
* Starts a Main activity in the specified profile.
*
* @param component The ComponentName of the activity to launch
@@ -916,7 +949,7 @@
}
ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
for (LauncherActivityInfoInternal internal : internals.getList()) {
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, user, internal);
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, internal);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index f0230e7..0806f1d 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,12 +26,12 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,21 +75,21 @@
*
* @hide
*/
- public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
+ @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
/**
* Returns system credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
+ @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
/**
* Returns user credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
+ @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
private final Context mContext;
private final ICredentialManager mService;
@@ -263,44 +263,6 @@
}
/**
- * Gets a list of all user configurable credential providers registered on the system. This API
- * is intended for browsers and settings apps.
- *
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void listEnabledProviders(
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- requireNonNull(executor, "executor must not be null");
- requireNonNull(callback, "callback must not be null");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "listEnabledProviders already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.listEnabledProviders(
- new ListEnabledProvidersTransport(executor, callback));
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Sets a list of all user configurable credential providers registered on the system. This API
* is intended for settings apps.
*
@@ -348,36 +310,43 @@
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device but will include test system providers as well.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServicesForTesting(
- @ProviderFilter int providerFilter) {
+ @TestApi
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- mContext.getUserId(),
- /* disableSystemAppVerificationForTests= */ true,
- providerFilter);
+ return mService.getCredentialProviderServicesForTesting(providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServices(
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServices(
int userId, @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- userId, /* disableSystemAppVerificationForTests= */ false, providerFilter);
+ return mService.getCredentialProviderServices(userId, providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -388,7 +357,9 @@
*
* @hide
*/
- public static boolean isServiceEnabled(Context context) {
+ @TestApi
+ public static boolean isServiceEnabled(@NonNull Context context) {
+ requireNonNull(context, "context must not be null");
if (context == null) {
return false;
}
@@ -578,33 +549,6 @@
}
}
- private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub {
- // TODO: listen for cancellation to release callback.
-
- private final Executor mExecutor;
- private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- mCallback;
-
- private ListEnabledProvidersTransport(
- Executor executor,
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- mExecutor = executor;
- mCallback = callback;
- }
-
- @Override
- public void onResponse(ListEnabledProvidersResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
- }
-
- @Override
- public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new ListEnabledProvidersException(errorType, message)));
- }
- }
-
private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
// TODO: listen for cancellation to release callback.
diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/CredentialProviderInfo.aidl
similarity index 63%
rename from core/java/android/credentials/IListEnabledProvidersCallback.aidl
rename to core/java/android/credentials/CredentialProviderInfo.aidl
index 3a8e25ed..30b7742 100644
--- a/core/java/android/credentials/IListEnabledProvidersCallback.aidl
+++ b/core/java/android/credentials/CredentialProviderInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,4 @@
package android.credentials;
-import android.credentials.ListEnabledProvidersResponse;
-
-/**
- * Listener for an listEnabledProviders request.
- *
- * @hide
- */
-interface IListEnabledProvidersCallback {
- oneway void onResponse(in ListEnabledProvidersResponse response);
- oneway void onError(String errorType, String message);
-}
\ No newline at end of file
+parcelable CredentialProviderInfo;
\ No newline at end of file
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
new file mode 100644
index 0000000..7276770
--- /dev/null
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a credential provider.
+ *
+ * @hide
+ */
+@TestApi
+public final class CredentialProviderInfo implements Parcelable {
+ @NonNull private final ServiceInfo mServiceInfo;
+ @NonNull private final List<String> mCapabilities = new ArrayList<>();
+ @Nullable private final CharSequence mOverrideLabel;
+ private final boolean mIsSystemProvider;
+ private final boolean mIsEnabled;
+
+ /**
+ * Constructs an information instance of the credential provider.
+ *
+ * @param builder the builder object.
+ */
+ private CredentialProviderInfo(@NonNull Builder builder) {
+ mServiceInfo = builder.mServiceInfo;
+ mCapabilities.addAll(builder.mCapabilities);
+ mIsSystemProvider = builder.mIsSystemProvider;
+ mIsEnabled = builder.mIsEnabled;
+ mOverrideLabel = builder.mOverrideLabel;
+ }
+
+ /** Returns true if the service supports the given {@code credentialType}, false otherwise. */
+ @NonNull
+ public boolean hasCapability(@NonNull String credentialType) {
+ return mCapabilities.contains(credentialType);
+ }
+
+ /** Returns the service info. */
+ @NonNull
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ /** Returns whether it is a system provider. */
+ public boolean isSystemProvider() {
+ return mIsSystemProvider;
+ }
+
+ /** Returns the service icon. */
+ @Nullable
+ public Drawable getServiceIcon(@NonNull Context context) {
+ return mServiceInfo.loadIcon(context.getPackageManager());
+ }
+
+ /** Returns the service label. */
+ @Nullable
+ public CharSequence getLabel(@NonNull Context context) {
+ if (mOverrideLabel != null) {
+ return mOverrideLabel;
+ }
+ return mServiceInfo.loadSafeLabel(context.getPackageManager());
+ }
+
+ /** Returns a list of capabilities this provider service can support. */
+ @NonNull
+ public List<String> getCapabilities() {
+ return Collections.unmodifiableList(mCapabilities);
+ }
+
+ /** Returns whether the provider is enabled by the user. */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /** Returns the component name for the service. */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mServiceInfo.getComponentName();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mServiceInfo, flags);
+ dest.writeBoolean(mIsSystemProvider);
+ dest.writeStringList(mCapabilities);
+ dest.writeBoolean(mIsEnabled);
+ TextUtils.writeToParcel(mOverrideLabel, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CredentialProviderInfo {"
+ + "serviceInfo="
+ + mServiceInfo
+ + ", "
+ + "isSystemProvider="
+ + mIsSystemProvider
+ + ", "
+ + "isEnabled="
+ + mIsEnabled
+ + ", "
+ + "overrideLabel="
+ + mOverrideLabel
+ + ", "
+ + "capabilities="
+ + String.join(",", mCapabilities)
+ + "}";
+ }
+
+ private CredentialProviderInfo(@NonNull Parcel in) {
+ mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
+ mIsSystemProvider = in.readBoolean();
+ in.readStringList(mCapabilities);
+ mIsEnabled = in.readBoolean();
+ mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
+ new Parcelable.Creator<CredentialProviderInfo>() {
+ @Override
+ public CredentialProviderInfo[] newArray(int size) {
+ return new CredentialProviderInfo[size];
+ }
+
+ @Override
+ public CredentialProviderInfo createFromParcel(@NonNull Parcel in) {
+ return new CredentialProviderInfo(in);
+ }
+ };
+
+ /** A builder for {@link CredentialProviderInfo} objects. */
+ public static final class Builder {
+
+ @NonNull private ServiceInfo mServiceInfo;
+ @NonNull private List<String> mCapabilities = new ArrayList<>();
+ private boolean mIsSystemProvider = false;
+ private boolean mIsEnabled = false;
+ @Nullable private CharSequence mOverrideLabel = null;
+
+ /**
+ * Creates a new builder.
+ *
+ * @param serviceInfo the service info of the credential provider service.
+ */
+ public Builder(@NonNull ServiceInfo serviceInfo) {
+ mServiceInfo = serviceInfo;
+ }
+
+ /** Sets whether it is a system provider. */
+ public @NonNull Builder setSystemProvider(boolean isSystemProvider) {
+ mIsSystemProvider = isSystemProvider;
+ return this;
+ }
+
+ /**
+ * Sets the label to be used instead of getting from the system (for unit tests).
+ *
+ * @hide
+ */
+ public @NonNull Builder setOverrideLabel(@NonNull CharSequence overrideLabel) {
+ mOverrideLabel = overrideLabel;
+ return this;
+ }
+
+ /** Sets a list of capabilities this provider service can support. */
+ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
+ mCapabilities.addAll(capabilities);
+ return this;
+ }
+
+ /** Sets whether it is enabled by the user. */
+ public @NonNull Builder setEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ return this;
+ }
+
+ /** Builds a new {@link CredentialProviderInfo} instance. */
+ public @NonNull CredentialProviderInfo build() {
+ return new CredentialProviderInfo(this);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 625fc8a..8c2cb5a 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,7 +18,7 @@
import java.util.List;
-import android.content.pm.ServiceInfo;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
@@ -27,7 +27,6 @@
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
@@ -45,8 +44,6 @@
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
- @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
-
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
@@ -55,6 +52,8 @@
boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);
- List<ServiceInfo> getCredentialProviderServices(in int userId, in boolean disableSystemAppVerificationForTests, in int providerFilter);
+ List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter);
+
+ List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter);
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 067ae4d..490e55b 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -28,6 +28,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.Surface;
@@ -53,6 +54,8 @@
private final int mDisplayIdToMirror;
private final boolean mWindowManagerMirroringEnabled;
private ArraySet<String> mDisplayCategories = null;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
private final float mRequestedRefreshRate;
private VirtualDisplayConfig(
@@ -65,6 +68,7 @@
@Nullable String uniqueId,
int displayIdToMirror,
boolean windowManagerMirroringEnabled,
+ ContentRecordingSession session,
@NonNull ArraySet<String> displayCategories,
float requestedRefreshRate) {
mName = name;
@@ -76,6 +80,7 @@
mUniqueId = uniqueId;
mDisplayIdToMirror = displayIdToMirror;
mWindowManagerMirroringEnabled = windowManagerMirroringEnabled;
+ mContentRecordingSession = session;
mDisplayCategories = displayCategories;
mRequestedRefreshRate = requestedRefreshRate;
}
@@ -156,6 +161,17 @@
}
/**
+ * Returns the recording session associated with this VirtualDisplay. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @Nullable
+ public ContentRecordingSession getContentRecordingSession() {
+ return mContentRecordingSession;
+ }
+
+ /**
* Returns the display categories.
*
* @see Builder#setDisplayCategories
@@ -186,6 +202,7 @@
dest.writeString8(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
dest.writeBoolean(mWindowManagerMirroringEnabled);
+ dest.writeTypedObject(mContentRecordingSession, flags);
dest.writeArraySet(mDisplayCategories);
dest.writeFloat(mRequestedRefreshRate);
}
@@ -211,6 +228,7 @@
&& Objects.equals(mUniqueId, that.mUniqueId)
&& mDisplayIdToMirror == that.mDisplayIdToMirror
&& mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled
+ && Objects.equals(mContentRecordingSession, that.mContentRecordingSession)
&& Objects.equals(mDisplayCategories, that.mDisplayCategories)
&& mRequestedRefreshRate == that.mRequestedRefreshRate;
}
@@ -219,8 +237,8 @@
public int hashCode() {
int hashCode = Objects.hash(
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
- mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
- mRequestedRefreshRate);
+ mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession,
+ mDisplayCategories, mRequestedRefreshRate);
return hashCode;
}
@@ -237,6 +255,7 @@
+ " mUniqueId=" + mUniqueId
+ " mDisplayIdToMirror=" + mDisplayIdToMirror
+ " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled
+ + " mContentRecordingSession=" + mContentRecordingSession
+ " mDisplayCategories=" + mDisplayCategories
+ " mRequestedRefreshRate=" + mRequestedRefreshRate
+ ")";
@@ -252,6 +271,7 @@
mUniqueId = in.readString8();
mDisplayIdToMirror = in.readInt();
mWindowManagerMirroringEnabled = in.readBoolean();
+ mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR);
mDisplayCategories = (ArraySet<String>) in.readArraySet(null);
mRequestedRefreshRate = in.readFloat();
}
@@ -283,6 +303,8 @@
private String mUniqueId = null;
private int mDisplayIdToMirror = DEFAULT_DISPLAY;
private boolean mWindowManagerMirroringEnabled = false;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
private ArraySet<String> mDisplayCategories = new ArraySet<>();
private float mRequestedRefreshRate = 0.0f;
@@ -375,6 +397,18 @@
}
/**
+ * Sets the recording session associated with this {@link VirtualDisplay}. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) {
+ mContentRecordingSession = session;
+ return this;
+ }
+
+ /**
* Sets the display categories.
*
* <p>The categories of the display indicate the type of activities allowed to run on that
@@ -435,6 +469,7 @@
mUniqueId,
mDisplayIdToMirror,
mWindowManagerMirroringEnabled,
+ mContentRecordingSession,
mDisplayCategories,
mRequestedRefreshRate);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index b749d69..cacde7f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -26,6 +26,8 @@
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.OnActivityPausedListener;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -1470,11 +1472,17 @@
if (activity == null || intent == null) {
throw new NullPointerException();
}
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("Foreground dispatch can only be enabled " +
+ "when your activity is resumed");
+ }
try {
TechListParcel parcel = null;
if (techLists != null && techLists.length > 0) {
parcel = new TechListParcel(techLists);
}
+ ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
+ mForegroundDispatchListener);
sService.setForegroundDispatch(intent, filters, parcel);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
@@ -1502,8 +1510,25 @@
throw new UnsupportedOperationException();
}
}
+ ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
+ mForegroundDispatchListener);
+ disableForegroundDispatchInternal(activity, false);
+ }
+
+ OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
+ @Override
+ public void onPaused(Activity activity) {
+ disableForegroundDispatchInternal(activity, true);
+ }
+ };
+
+ void disableForegroundDispatchInternal(Activity activity, boolean force) {
try {
sService.setForegroundDispatch(null, null, null);
+ if (!force && !activity.isResumed()) {
+ throw new IllegalStateException("You must disable foreground dispatching " +
+ "while your activity is still resumed");
+ }
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 07d5001..5b527c7 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -979,6 +979,19 @@
}
/**
+ * An unrestricted version of getType, which does not reveal sensitive information
+ */
+ @Override
+ public final @Nullable String getTypeAnonymous(@NonNull Uri uri) {
+ switch (mMatcher.match(uri)) {
+ case MATCH_ROOT:
+ return DocumentsContract.Root.MIME_TYPE_ITEM;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Implementation is provided by the parent class. Can be overridden to
* provide additional functionality, but subclasses <em>must</em> always
* call the superclass. If the superclass returns {@code null}, the subclass
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef00774..045ba1f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5678,6 +5678,36 @@
public static final String LOCALE_PREFERENCES = "locale_preferences";
/**
+ * Setting to enable camera flash notification feature.
+ * <ul>
+ * <li> 0 = Off
+ * <li> 1 = On
+ * </ul>
+ * @hide
+ */
+ public static final String CAMERA_FLASH_NOTIFICATION = "camera_flash_notification";
+
+ /**
+ * Setting to enable screen flash notification feature.
+ * <ul>
+ * <li> 0 = Off
+ * <li> 1 = On
+ * </ul>
+ * @hide
+ */
+ public static final String SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
+
+ /**
+ * Integer property that specifes the color for screen flash notification as a
+ * packed 32-bit color.
+ *
+ * @see android.graphics.Color#argb
+ * @hide
+ */
+ public static final String SCREEN_FLASH_NOTIFICATION_COLOR =
+ "screen_flash_notification_color_global";
+
+ /**
* IMPORTANT: If you add a new public settings you also have to add it to
* PUBLIC_SETTINGS below. If the new setting is hidden you have to add
* it to PRIVATE_SETTINGS below. Also add a validator that can validate
@@ -5810,6 +5840,9 @@
PRIVATE_SETTINGS.add(TOUCHPAD_NATURAL_SCROLLING);
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_TO_CLICK);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
+ PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
+ PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
+ PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
}
/**
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 7e98bc7..e9cebd2 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -27,6 +27,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
/**
* A credential entry that is to be displayed on the account selector that is presented to the
* user.
@@ -56,7 +58,7 @@
@SuppressLint("ParcelNotFinal")
public class CredentialEntry implements Parcelable {
/** The request option that corresponds to this entry. **/
- private final @Nullable BeginGetCredentialOption mBeginGetCredentialOption;
+ private final @Nullable String mBeginGetCredentialOptionId;
/** The type of the credential entry to be shown on the UI. */
private final @NonNull String mType;
@@ -72,19 +74,51 @@
* to respond to query phase {@link CredentialProviderService#onBeginGetCredential}
* credential retrieval requests.
*
+ * @param beginGetCredentialOptionId the beginGetCredentialOptionId to be retrieved from
+ * {@link BeginGetCredentialOption#getId()} - the request option for which this CredentialEntry
+ * is being constructed This helps maintain an association
+ * such that when the user selects this entry, providers can
+ * receive the complete corresponding
+ * {@link GetCredentialRequest}.
+ * @param type the type of the credential for which this credential entry is being created
+ * @param slice the slice containing the metadata to be shown on the UI. Must be
+ * constructed through the androidx.credentials jetpack library.
+ *
+ * @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type}
+ * is null, or empty
+ */
+ public CredentialEntry(@NonNull String beginGetCredentialOptionId, @NonNull String type,
+ @NonNull Slice slice) {
+ mBeginGetCredentialOptionId = Preconditions.checkStringNotEmpty(
+ beginGetCredentialOptionId, "beginGetCredentialOptionId must not be "
+ + "null, or empty");
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be null, or "
+ + "empty");
+ mSlice = requireNonNull(slice, "slice must not be null");
+ }
+
+ /**
+ * Creates an entry that is associated with a {@link BeginGetCredentialOption} request.
+ * Providers must use this constructor when they extend from {@link CredentialProviderService}
+ * to respond to query phase {@link CredentialProviderService#onBeginGetCredential}
+ * credential retrieval requests.
+ *
* @param beginGetCredentialOption the request option for which this credential entry is
* being constructed This helps maintain an association,
* such that when the user selects this entry, providers
- * can receive the conmplete corresponding request.
+ * can receive the complete corresponding request.
* @param slice the slice containing the metadata to be shown on the UI. Must be
* constructed through the androidx.credentials jetpack library.
*/
public CredentialEntry(@NonNull BeginGetCredentialOption beginGetCredentialOption,
@NonNull Slice slice) {
- mBeginGetCredentialOption = requireNonNull(beginGetCredentialOption,
- "beginGetCredentialOption must not be null");
- mType = requireNonNull(mBeginGetCredentialOption.getType(),
- "type must not be null");
+ requireNonNull(beginGetCredentialOption, "beginGetCredentialOption must not"
+ + " be null");
+ mBeginGetCredentialOptionId = Preconditions.checkStringNotEmpty(
+ beginGetCredentialOption.getId(), "Id in beginGetCredentialOption "
+ + "must not be null");
+ mType = Preconditions.checkStringNotEmpty(beginGetCredentialOption.getType(),
+ "type in beginGetCredentialOption must not be null");
mSlice = requireNonNull(slice, "slice must not be null");
}
@@ -97,11 +131,9 @@
* @param slice the slice containing the metadata to be shown on the UI. Must be
* constructed through the androidx.credentials jetpack library.
*
- * @hide
*/
- // TODO: Unhide this constructor when the registry APIs are stable
public CredentialEntry(@NonNull String type, @NonNull Slice slice) {
- mBeginGetCredentialOption = null;
+ mBeginGetCredentialOptionId = null;
mType = requireNonNull(type, "type must not be null");
mSlice = requireNonNull(slice, "slice must not be null");
}
@@ -110,7 +142,7 @@
requireNonNull(in, "parcel must not be null");
mType = in.readString8();
mSlice = in.readTypedObject(Slice.CREATOR);
- mBeginGetCredentialOption = in.readTypedObject(BeginGetCredentialOption.CREATOR);
+ mBeginGetCredentialOptionId = in.readString8();
}
@NonNull
@@ -136,15 +168,16 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeTypedObject(mSlice, flags);
- dest.writeTypedObject(mBeginGetCredentialOption, flags);
+ dest.writeString8(mBeginGetCredentialOptionId);
}
/**
- * Returns the request option for which this credential entry has been constructed.
+ * Returns the id of the {@link BeginGetCredentialOption} for which this credential
+ * entry has been constructed.
*/
@NonNull
- public BeginGetCredentialOption getBeginGetCredentialOption() {
- return mBeginGetCredentialOption;
+ public String getBeginGetCredentialOptionId() {
+ return mBeginGetCredentialOptionId;
}
/**
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
similarity index 69%
rename from core/java/android/service/credentials/CredentialProviderInfo.java
rename to core/java/android/service/credentials/CredentialProviderInfoFactory.java
index b5464db..fd9360f 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -34,40 +34,27 @@
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.credentials.CredentialManager;
-import android.graphics.drawable.Drawable;
+import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
-import android.text.TextUtils;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
- * {@link ServiceInfo} and meta-data about a credential provider.
+ * {@link CredentialProviderInfo} generator.
*
* @hide
*/
-public final class CredentialProviderInfo {
- private static final String TAG = "CredentialProviderInfo";
-
- @NonNull
- private final ServiceInfo mServiceInfo;
- @NonNull
- private final List<String> mCapabilities;
-
- @NonNull
- private final Context mContext;
- @Nullable
- private final Drawable mIcon;
- @Nullable
- private final CharSequence mLabel;
- private final boolean mIsSystemProvider;
+public final class CredentialProviderInfoFactory {
+ private static final String TAG = "CredentialProviderInfoFactory";
/**
* Constructs an information instance of the credential provider.
@@ -79,14 +66,18 @@
* @throws PackageManager.NameNotFoundException If provider service is not found
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(@NonNull Context context,
- @NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider)
+ public static CredentialProviderInfo create(
+ @NonNull Context context,
+ @NonNull ComponentName serviceComponent,
+ int userId,
+ boolean isSystemProvider)
throws PackageManager.NameNotFoundException {
- this(
+ return create(
context,
getServiceInfoOrThrow(serviceComponent, userId),
isSystemProvider,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ /* isEnabled= */ false);
}
/**
@@ -98,13 +89,16 @@
* @param isSystemProvider whether the provider app is a system provider
* @param disableSystemAppVerificationForTests whether to disable system app permission
* verification so that tests can install system providers
+ * @param isEnabled whether the user enabled this provider
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(
+ public static CredentialProviderInfo create(
@NonNull Context context,
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ boolean isEnabled)
+ throws SecurityException {
verifyProviderPermission(serviceInfo);
if (isSystemProvider) {
if (!isValidSystemProvider(
@@ -114,23 +108,11 @@
"Provider is not a valid system provider: " + serviceInfo);
}
}
- mIsSystemProvider = isSystemProvider;
- mContext = requireNonNull(context, "context must not be null");
- mServiceInfo = requireNonNull(serviceInfo, "serviceInfo must not be null");
- mCapabilities = new ArrayList<>();
- mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
- mLabel =
- mServiceInfo.loadSafeLabel(
- mContext.getPackageManager(),
- 0 /* do not ellipsize */,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
- Log.i(
- TAG,
- "mLabel is : "
- + mLabel
- + ", for: "
- + mServiceInfo.getComponentName().flattenToString());
- populateProviderCapabilities(context, serviceInfo);
+
+ return populateMetadata(context, serviceInfo)
+ .setSystemProvider(isSystemProvider)
+ .setEnabled(isEnabled)
+ .build();
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -138,19 +120,14 @@
if (permission.equals(serviceInfo.permission)) {
return;
}
-
- Slog.e(
- TAG,
- "Credential Provider Service from : "
- + serviceInfo.packageName
- + "does not require permission"
- + permission);
throw new SecurityException(
"Service does not require the expected permission : " + permission);
}
private static boolean isSystemProviderWithValidPermission(
ServiceInfo serviceInfo, Context context) {
+ requireNonNull(context, "context must not be null");
+
final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE;
try {
ApplicationInfo appInfo =
@@ -177,67 +154,88 @@
Context context,
ServiceInfo serviceInfo,
boolean disableSystemAppVerificationForTests) {
- boolean isValidSystemTestProvider =
- isTestSystemProvider(serviceInfo, disableSystemAppVerificationForTests);
- if (isValidSystemTestProvider) {
- return true;
+ requireNonNull(context, "context must not be null");
+
+ if (disableSystemAppVerificationForTests) {
+ Bundle metadata = serviceInfo.metaData;
+ if (metadata == null) {
+ Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
+ return false;
+ }
+ return metadata.getBoolean(
+ CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
}
+
return isSystemProviderWithValidPermission(serviceInfo, context);
}
- private static boolean isTestSystemProvider(
- ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) {
- if (!disableSystemAppVerificationForTests) {
- return false;
- }
+ private static CredentialProviderInfo.Builder populateMetadata(
+ @NonNull Context context, ServiceInfo serviceInfo) {
+ requireNonNull(context, "context must not be null");
- Bundle metadata = serviceInfo.metaData;
- if (metadata == null) {
- Slog.e(TAG, "metadata is null: " + serviceInfo);
- return false;
- }
- return metadata.getBoolean(CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
- }
-
- private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
+ final CredentialProviderInfo.Builder builder =
+ new CredentialProviderInfo.Builder(serviceInfo);
final PackageManager pm = context.getPackageManager();
+
+ // 1. Get the metadata for the service.
+ final Bundle metadata = serviceInfo.metaData;
+ if (metadata == null) {
+ Log.i(TAG, "populateMetadata - metadata is null");
+ return builder;
+ }
+
+ // 2. Extract the capabilities from the bundle.
try {
- Bundle metadata = serviceInfo.metaData;
Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
if (metadata == null || resources == null) {
- Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
- return;
+ Log.i(TAG, "populateMetadata - resources is null");
+ return builder;
}
- String[] capabilities = resources.getStringArray(metadata.getInt(
- CredentialProviderService.CAPABILITY_META_DATA_KEY));
- if (capabilities == null || capabilities.length == 0) {
- Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
- return;
- }
-
- for (String capability : capabilities) {
- if (capability.isEmpty()) {
- Slog.i(TAG, "Skipping empty capability");
- continue;
- }
- Slog.i(TAG, "Capabilities found for provider: " + capability);
- mCapabilities.add(capability);
- }
+ builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, e.getMessage());
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, e.getMessage());
}
+
+ return builder;
}
- private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent,
- int userId) throws PackageManager.NameNotFoundException {
+ private static List<String> populateProviderCapabilities(
+ Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
+ List<String> output = new ArrayList<>();
+ String[] capabilities = new String[0];
+
try {
- ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.GET_META_DATA,
- userId);
+ capabilities =
+ resources.getStringArray(
+ metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
+ }
+
+ if (capabilities == null || capabilities.length == 0) {
+ Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+ return output;
+ }
+
+ for (String capability : capabilities) {
+ if (capability.isEmpty()) {
+ Slog.e(TAG, "Skipping empty capability");
+ continue;
+ }
+ Slog.e(TAG, "Capabilities found for provider: " + capability);
+ output.add(capability);
+ }
+ return output;
+ }
+
+ private static ServiceInfo getServiceInfoOrThrow(
+ @NonNull ComponentName serviceComponent, int userId)
+ throws PackageManager.NameNotFoundException {
+ try {
+ ServiceInfo si =
+ AppGlobals.getPackageManager()
+ .getServiceInfo(serviceComponent, PackageManager.GET_META_DATA, userId);
if (si != null) {
return si;
}
@@ -256,6 +254,8 @@
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests) {
+ requireNonNull(context, "context must not be null");
+
final List<ServiceInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -268,15 +268,20 @@
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (disableSystemAppVerificationForTests) {
+ if (serviceInfo != null) {
+ services.add(serviceInfo);
+ }
+ continue;
+ }
+
try {
- PackageManager.ApplicationInfoFlags appInfoFlags =
- disableSystemAppVerificationForTests
- ? PackageManager.ApplicationInfoFlags.of(0)
- : PackageManager.ApplicationInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY);
ApplicationInfo appInfo =
context.getPackageManager()
- .getApplicationInfo(serviceInfo.packageName, appInfoFlags);
+ .getApplicationInfo(
+ serviceInfo.packageName,
+ PackageManager.ApplicationInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo == null || serviceInfo == null) {
continue;
@@ -300,19 +305,22 @@
public static List<CredentialProviderInfo> getAvailableSystemServices(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
+
final List<CredentialProviderInfo> providerInfos = new ArrayList<>();
for (ServiceInfo si :
getAvailableSystemServiceInfos(
context, userId, disableSystemAppVerificationForTests)) {
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
si,
/* isSystemProvider= */ true,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(si));
if (cpi.isSystemProvider()) {
providerInfos.add(cpi);
} else {
@@ -325,45 +333,12 @@
return providerInfos;
}
- /**
- * Returns true if the service supports the given {@code credentialType}, false otherwise.
- */
- @NonNull
- public boolean hasCapability(@NonNull String credentialType) {
- return mCapabilities.contains(credentialType);
- }
+ private static @Nullable PackagePolicy getDeviceManagerPolicy(
+ @NonNull Context context, int userId) {
+ Context newContext = context.createContextAsUser(UserHandle.of(userId), 0);
- /** Returns the service info. */
- @NonNull
- public ServiceInfo getServiceInfo() {
- return mServiceInfo;
- }
-
- public boolean isSystemProvider() {
- return mIsSystemProvider;
- }
-
- /** Returns the service icon. */
- @Nullable
- public Drawable getServiceIcon() {
- return mIcon;
- }
-
- /** Returns the service label. */
- @Nullable
- public CharSequence getServiceLabel() {
- return mLabel;
- }
-
- /** Returns an immutable list of capabilities this provider service can support. */
- @NonNull
- public List<String> getCapabilities() {
- return Collections.unmodifiableList(mCapabilities);
- }
-
- private static @Nullable PackagePolicy getDeviceManagerPolicy(@NonNull Context context) {
try {
- DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
return dpm.getCredentialManagerPolicy();
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
@@ -381,21 +356,53 @@
public static List<CredentialProviderInfo> getCredentialProviderServices(
@NonNull Context context,
int userId,
- boolean disableSystemAppVerificationForTests,
- int providerFilter) {
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
- PackagePolicy pp = getDeviceManagerPolicy(context);
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
// Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = false;
ProviderGenerator generator =
new ProviderGenerator(
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
- getUserProviders(context, userId, disableSystemAppVerificationForTests));
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
generator.addSystemProviders(
- getAvailableSystemServices(context, userId, disableSystemAppVerificationForTests));
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ return generator.getProviders();
+ }
+
+ /**
+ * Returns the valid credential provider services available for the user with the given {@code
+ * userId}. Includes test providers.
+ */
+ @NonNull
+ public static List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @NonNull Context context,
+ int userId,
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
+ requireNonNull(context, "context must not be null");
+
+ // Get the device policy.
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
+
+ // Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = true;
+ ProviderGenerator generator =
+ new ProviderGenerator(
+ context, pp, disableSystemAppVerificationForTests, providerFilter);
+ generator.addUserProviders(
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ generator.addSystemProviders(
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
return generator.getProviders();
}
@@ -484,7 +491,8 @@
private static List<CredentialProviderInfo> getUserProviders(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager()
@@ -496,11 +504,12 @@
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
serviceInfo,
/* isSystemProvider= */ false,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(serviceInfo));
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 94384b0..f74f533 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -533,7 +533,7 @@
* the calling package or if the calling user cannot act on behalf of the user from the
* {@code context}.</li>
* <li> {@link IllegalArgumentException} if the user of the {@code context} is not the
- * current user.</li>
+ * current user. Only thrown for apps targeting {@link Build.VERSION_CODES#TIRAMISU}</li>
* </ul>
*/
public static final void requestListeningState(Context context, ComponentName component) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2ae882c..6201b3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 406f446..24a0355 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -244,6 +244,10 @@
window = iwindow;
}
+ public @Nullable IBinder getWindowToken() {
+ return windowToken;
+ }
+
public IWindow getWindow() {
if (window != null) {
return window;
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 85aea85..4a7ed64 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -32,9 +32,10 @@
*
* Use {@link #obtain} to retrieve a new instance of the class when you are going
* to begin tracking. Put the motion events you receive into it with
- * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call
- * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
- * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
+ * {@link #addMovement(MotionEvent)}. When you want to determine the velocity, call
+ * {@link #computeCurrentVelocity(int)} and then call the velocity-getter methods like
+ * {@link #getXVelocity(int)}, {@link #getYVelocity(int)}, or {@link #getAxisVelocity(int, int)}
+ * to retrieve velocity for different axes and/or pointer IDs.
*/
public final class VelocityTracker {
private static final SynchronizedPool<VelocityTracker> sPool =
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 869efc69..8bf3232 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8920,7 +8920,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8928,6 +8929,9 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -16159,7 +16163,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -26375,6 +26380,9 @@
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 807af5b..a8b4da1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -79,6 +80,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -92,12 +94,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -895,6 +899,15 @@
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -986,6 +999,8 @@
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8598,6 +8613,9 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8614,6 +8632,69 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dfb11bc..35ed88f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -872,6 +872,42 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 6ddfcb8..8cff066 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -562,11 +562,11 @@
final int numberEvents = mEvents.size();
final String reasonString = getFlushReasonAsString(reason);
- if (sDebug) {
+ if (sVerbose) {
ContentCaptureEvent event = mEvents.get(numberEvents - 1);
String forceString = (reason == FLUSH_REASON_FORCE_FLUSH) ? ". The force flush event "
+ ContentCaptureEvent.getTypeAsString(event.getType()) : "";
- Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)
+ Log.v(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)
+ forceString);
}
if (mFlushHistory != null) {
diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java
index 8db5a5e..42bb674 100644
--- a/core/java/android/window/WindowInfosListener.java
+++ b/core/java/android/window/WindowInfosListener.java
@@ -16,6 +16,8 @@
package android.window;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.graphics.Matrix;
import android.util.Pair;
import android.util.Size;
@@ -49,10 +51,13 @@
/**
* Register the WindowInfosListener.
*
+ * Registering WindowInfosListeners should only be done within system_server and shell.
+ *
* @return The cached values for InputWindowHandles and DisplayInfos. This is the last updated
* value that was sent from SurfaceFlinger to this particular process. If there was nothing
* registered previously, then the data can be empty.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
public Pair<InputWindowHandle[], DisplayInfo[]> register() {
return nativeRegister(mNativeListener);
}
@@ -65,8 +70,12 @@
}
private static native long nativeCreate(WindowInfosListener thiz);
+
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
private static native Pair<InputWindowHandle[], DisplayInfo[]> nativeRegister(long ptr);
+
private static native void nativeUnregister(long ptr);
+
private static native long nativeGetFinalizer();
/**
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
new file mode 100644
index 0000000..429156f
--- /dev/null
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.InputConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.InputWindowHandle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper class to provide access to WindowInfosListener within tests.
+ *
+ * @hide
+ */
+@TestApi
+public class WindowInfosListenerForTest {
+
+ /**
+ * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}.
+ */
+ public static class WindowInfo {
+ /**
+ * The window's token.
+ */
+ @NonNull
+ public final IBinder windowToken;
+
+ /**
+ * The window's name.
+ */
+ @NonNull
+ public final String name;
+
+ /**
+ * The window's position and size in display space.
+ */
+ @NonNull
+ public final Rect bounds;
+
+ WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+ this.windowToken = windowToken;
+ this.name = name;
+ this.bounds = bounds;
+ }
+ }
+
+ private static final String TAG = "WindowInfosListenerForTest";
+
+ private ArrayMap<Consumer<List<WindowInfo>>, WindowInfosListener> mListeners;
+
+ public WindowInfosListenerForTest() {
+ mListeners = new ArrayMap<>();
+ }
+
+ /**
+ * Register a listener that is called when the system's list of visible windows has changes in
+ * position or visibility.
+ *
+ * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances
+ * where the first value is the topmost window.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ public void addWindowInfosListener(
+ @NonNull Consumer<List<WindowInfo>> consumer) {
+ var calledWithInitialState = new CountDownLatch(1);
+ var listener = new WindowInfosListener() {
+ @Override
+ public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+ DisplayInfo[] displayInfos) {
+ try {
+ calledWithInitialState.await();
+ } catch (InterruptedException exception) {
+ Log.e(TAG,
+ "Exception thrown while waiting for listener to be called with "
+ + "initial state");
+ }
+ consumer.accept(buildWindowInfos(windowHandles));
+ }
+ };
+ mListeners.put(consumer, listener);
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState =
+ listener.register();
+ consumer.accept(buildWindowInfos(initialState.first));
+ calledWithInitialState.countDown();
+ }
+
+ /**
+ * Unregisters the listener.
+ */
+ public void removeWindowInfosListener(@NonNull Consumer<List<WindowInfo>> consumer) {
+ WindowInfosListener listener = mListeners.remove(consumer);
+ if (listener == null) {
+ return;
+ }
+ listener.unregister();
+ }
+
+ private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) {
+ var windowInfos = new ArrayList<WindowInfo>(windowHandles.length);
+ for (var handle : windowHandles) {
+ if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) {
+ continue;
+ }
+ var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
+ handle.frameBottom);
+ windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+ }
+ return windowInfos;
+ }
+}
diff --git a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
index 6c01780..2a9025d 100644
--- a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
@@ -61,7 +61,7 @@
final int size = mPendingRequests.size();
if (mVerbose) Slog.v(mTag, "Sending " + size + " pending requests");
for (int i = 0; i < size; i++) {
- mPendingRequests.get(i).run();
+ handlePendingRequest(mPendingRequests.get(i));
}
mPendingRequests.clear();
}
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index d5f7ba5..18414cf 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -107,7 +107,7 @@
private int mServiceExitSubReason;
/** Requests that have been scheduled, but that are not finished yet */
- private final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
+ protected final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
/**
* Callback called when the service dies.
@@ -673,6 +673,11 @@
mCancelled = true;
}
+ S service = mWeakService.get();
+ if (service != null) {
+ service.finishRequest(this);
+ }
+
onCancel();
return true;
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 4bc567a..ad54004 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -20,20 +20,21 @@
#include <android_runtime/AndroidRuntime.h>
#include <input/InputTransport.h>
+#include <inttypes.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Looper.h>
+
+#include <optional>
+#include <unordered_map>
+
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
#include "core_jni_helpers.h"
-#include <inttypes.h>
-#include <unordered_map>
-
-
using android::base::Result;
namespace android {
@@ -67,7 +68,7 @@
jobject mSenderWeakGlobal;
InputPublisher mInputPublisher;
sp<MessageQueue> mMessageQueue;
- std::unordered_map<uint32_t, uint32_t> mPublishedSeqMap;
+ std::unordered_map<uint32_t, std::optional<uint32_t>> mPublishedSeqMap;
uint32_t mNextPublishedSeq;
@@ -165,8 +166,14 @@
getInputChannelName().c_str(), status);
return status;
}
+ // mPublishedSeqMap tracks all sequences published from this sender. Only the last
+ // sequence number is used to signal this motion event is finished.
+ if (i == event->getHistorySize()) {
+ mPublishedSeqMap.emplace(publishedSeq, seq);
+ } else {
+ mPublishedSeqMap.emplace(publishedSeq, std::nullopt);
+ }
}
- mPublishedSeqMap.emplace(publishedSeq, seq);
return OK;
}
@@ -277,8 +284,16 @@
// does something wrong and sends bad data. Just ignore and process other events.
return true;
}
- const uint32_t seq = it->second;
+
+ const std::optional<uint32_t> seqOptional = it->second;
mPublishedSeqMap.erase(it);
+ // If this optional does not have a value, it means we are processing an event that had history
+ // and was split. There are more events coming, so we can't call 'dispatchInputEventFinished'
+ // yet. The final split event will have a valid sequence number.
+ if (!seqOptional.has_value()) {
+ return true;
+ }
+ const uint32_t seq = seqOptional.value();
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 146ac9d..7503dde4 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -115,6 +115,9 @@
optional SettingProto sound_cache = 2;
optional SettingProto light_pulse = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto vibration_intensity = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto camera_flash_notification = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto screen_flash_notification = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto screen_flash_notification_color_global = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Notification notification = 17;
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 3c4bac8..b71995f 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -178,3 +178,12 @@
" > $(out)",
tools: ["xmllint"],
}
+
+filegroup {
+ name: "frameworks-base-core-AndroidManifest.xml",
+ srcs: ["AndroidManifest.xml"],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5a0ac0f..cb6c092 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -822,6 +822,8 @@
<protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
<protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
<protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
deleted file mode 100644
index 17b064c..0000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ /dev/null
@@ -1,123 +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.companion.virtual.sensor;
-
-import static android.hardware.Sensor.TYPE_ACCELEROMETER;
-import static android.hardware.SensorDirectChannel.RATE_STOP;
-import static android.hardware.SensorDirectChannel.RATE_VERY_FAST;
-import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER;
-import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.hardware.Sensor;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorConfigTest {
-
- private static final String SENSOR_NAME = "VirtualSensorName";
- private static final String SENSOR_VENDOR = "VirtualSensorVendor";
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorConfig originalConfig =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setVendor(SENSOR_VENDOR)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalConfig.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorConfig recreatedConfig =
- VirtualSensorConfig.CREATOR.createFromParcel(parcel);
- assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
- assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
- assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
- assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST);
- assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE);
- // From hardware/libhardware/include/hardware/sensors-base.h:
- // 0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE)
- // 0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER)
- // 7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT
- assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7);
- }
-
- @Test
- public void virtualSensorConfig_invalidName_throwsException() {
- assertThrows(
- NullPointerException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, null));
- }
-
- @Test
- public void virtualSensorConfig_invalidType_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(Sensor.TYPE_ALL, SENSOR_NAME));
-
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(0, SENSOR_NAME));
- }
-
- @Test
- public void hardwareBufferDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE));
- }
-
- @Test
- public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build());
- }
-
- @Test
- public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .build());
- }
-
- @Test
- public void sensorConfig_onlyRequiredFields() {
- final VirtualSensorConfig config =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
- assertThat(config.getVendor()).isNull();
- assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP);
- assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0);
- assertThat(config.getFlags()).isEqualTo(0);
- }
-}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
deleted file mode 100644
index c260ef9..0000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
+++ /dev/null
@@ -1,82 +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.companion.virtual.sensor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Parcel;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorEventTest {
-
- private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
- private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalEvent.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorEvent recreatedEvent =
- VirtualSensorEvent.CREATOR.createFromParcel(parcel);
- assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
- assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
- }
-
- @Test
- public void sensorEvent_nullValues() {
- assertThrows(
- IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
- }
-
- @Test
- public void sensorEvent_noValues() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorEvent.Builder(new float[0]).build());
- }
-
- @Test
- public void sensorEvent_noTimestamp_usesCurrentTime() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
- assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
- }
-
- @Test
- public void sensorEvent_created() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- }
-}
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 43334ab..e31d5ae 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -30,6 +30,7 @@
import android.app.Activity;
import android.app.slice.Slice;
import android.content.Context;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -47,6 +48,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -59,6 +61,17 @@
@Mock
private Activity mMockActivity;
+ private static final int TEST_USER_ID = 1;
+ private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO =
+ new CredentialProviderInfo.Builder(new ServiceInfo())
+ .setSystemProvider(true)
+ .setOverrideLabel("test")
+ .addCapabilities(Arrays.asList("passkey"))
+ .setEnabled(true)
+ .build();
+ private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST =
+ Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
+
private GetCredentialRequest mGetRequest;
private CreateCredentialRequest mCreateRequest;
@@ -438,95 +451,53 @@
}
@Test
- public void testListEnabledProviders_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, null, result -> {
- }));
-
+ public void testGetCredentialProviderServices_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, mExecutor, null));
-
+ public void testGetCredentialProviderServices_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_alreadyCancelled() throws RemoteException {
- final CancellationSignal cancellation = new CancellationSignal();
- cancellation.cancel();
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, result -> {
- });
-
- verify(mMockCredentialManagerService, never()).listEnabledProviders(any());
+ public void testGetCredentialProviderServices_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_cancel() throws RemoteException {
- final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
- final CancellationSignal cancellation = new CancellationSignal();
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(any())).thenReturn(serviceSignal);
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, callback);
-
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- cancellation.cancel();
- verify(serviceSignal).cancel();
+ public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_failed() throws RemoteException {
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersException.class);
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- final String errorType = "type";
- callbackCaptor.getValue().onError("type", "unknown error");
- verify(callback).onError(errorCaptor.capture());
-
- assertThat(errorCaptor.getValue().getType()).isEqualTo(errorType);
+ public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_success() throws RemoteException {
- ListEnabledProvidersResponse response = ListEnabledProvidersResponse.create(
- List.of("foo", "bar", "baz"));
+ public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+ }
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
+ private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServices(
+ TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersResponse> responseCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersResponse.class);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter);
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+ }
- verify(mMockCredentialManagerService).listEnabledProviders(any());
+ private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(
+ testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- callbackCaptor.getValue().onResponse(response);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServicesForTesting(testFilter);
- verify(callback).onResult(responseCaptor.capture());
- assertThat(responseCaptor.getValue().getProviderComponentNames()).containsExactlyElementsIn(
- response.getProviderComponentNames());
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
}
@Test
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index feedb7d..9ac84a6 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -25,13 +25,11 @@
/**
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
- * display adjustment capability.
- *
- * It is a combination of a set of metadata describing how to apply the gainmap, as well as either
- * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
+ * display adjustment capability. It is a combination of a set of metadata describing how to apply
+ * the gainmap, as well as either a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
* (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
* channel Bitmap that represents the gainmap data itself.
- *
+ * <p>
* When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
* hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
* HDR headroom is available.
@@ -45,7 +43,7 @@
* image, often at a lower resolution (such as 1/4th), along with some metadata to describe
* how to apply the gainmap. The gainmap image itself is then a greyscale image representing
* the transformation to apply onto the base image to reconstruct an HDR rendition of it.
- *
+ * <p>
* As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
* {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
* the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
@@ -55,25 +53,27 @@
* When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
* automatically applied. In such situations, the following steps are appropriate to render the
* gainmap in combination with the base image.
- *
+ * <p>
* Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
* this display. Let B be the pixel value from the base image in a color space that has the
* primaries of the base image and a linear transfer function. Let G be the pixel value from the
* gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
* as follows:
- *
+ * <p>
* First, let W be a weight parameter determining how much the gainmap will be applied.
+ * <pre class="prettyprint">
* W = clamp((log(H) - log(minDisplayRatioForHdrTransition)) /
- * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)
+ * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)</pre>
*
* Next, let L be the gainmap value in log space. We compute this from the value G that was
* sampled from the texture as follows:
- * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))
- *
+ * <pre class="prettyprint">
+ * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))</pre>
* Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
* compute:
- * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
- *
+ * <pre class="prettyprint">
+ * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr</pre>
+ * <p>
* In the above math, log() is a natural logarithm and exp() is natural exponentiation. The base
* for these functions cancels out and does not affect the result, so other bases may be used
* if preferred.
@@ -150,7 +150,6 @@
/**
* Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMin(float r, float g, float b) {
nSetRatioMin(mNativePtr, r, g, b);
}
@@ -169,7 +168,6 @@
/**
* Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMax(float r, float g, float b) {
nSetRatioMax(mNativePtr, r, g, b);
}
@@ -188,7 +186,6 @@
/**
* Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setGamma(float r, float g, float b) {
nSetGamma(mNativePtr, r, g, b);
}
@@ -208,7 +205,6 @@
* Sets the sdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonSdr(float r, float g, float b) {
nSetEpsilonSdr(mNativePtr, r, g, b);
}
@@ -228,7 +224,6 @@
* Sets the hdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonHdr(float r, float g, float b) {
nSetEpsilonHdr(mNativePtr, r, g, b);
}
@@ -248,8 +243,7 @@
* Sets the hdr/sdr ratio at which point the gainmap is fully applied.
* @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
*/
- @NonNull
- public void setDisplayRatioForFullHdr(float max) {
+ public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
if (!Float.isFinite(max) || max < 1f) {
throw new IllegalArgumentException(
"setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
@@ -269,7 +263,6 @@
* Sets the hdr/sdr ratio below which only the SDR image is displayed.
* @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
*/
- @NonNull
public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
if (!Float.isFinite(min) || min < 1f) {
throw new IllegalArgumentException(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 480bf93..53bf42a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -39,6 +39,9 @@
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
+ // Fixed string used in WMShellFlickerTests
+ protected static final String LAYER_NAME = "PipContentOverlay";
+
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
@@ -86,7 +89,7 @@
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.setColorLayer()
.build();
}
@@ -139,7 +142,7 @@
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -194,7 +197,7 @@
prepareAppIconOverlay(activityInfo);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f11836e..e9d2571 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1594,7 +1594,7 @@
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6b0337d..a91a342 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -804,7 +804,7 @@
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo);
} else {
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 70d3b35..146abea 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
@@ -764,17 +764,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- }
- mSyncQueue.queue(wct);
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
return;
}
@@ -797,13 +789,9 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
return;
}
@@ -822,13 +810,8 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
return;
}
@@ -838,6 +821,49 @@
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
@@ -894,23 +920,25 @@
if (options == null) options = new Bundle();
addActivityOptions(options, mMainStage);
- options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
-
- // TODO(b/268008375): Merge APIs to start a split pair into one.
- if (mainTaskId != INVALID_TASK_ID) {
- wct.startTask(mainTaskId, options);
- } else if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
- } else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
- }
-
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
- mSyncQueue.queue(wct);
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
+ wct.startTask(mainTaskId, options);
+ mSyncQueue.queue(wct);
+ } else {
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
+ }
+
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
});
@@ -967,6 +995,54 @@
return activityOptions.toBundle();
}
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ return transition;
+ }
+
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
@@ -993,6 +1069,27 @@
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
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 da80c6f..5c99209 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
@@ -80,7 +80,8 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
+ appExistAtStart = false)
@Presubmit
@Test
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
index 5ecec4d..3125f08 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
@@ -72,6 +72,7 @@
public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
private final DreamManager mDreamManager;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@
@Inject
public LowLightDreamManager(
DreamManager dreamManager,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
@Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
mDreamManager = dreamManager;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mLowLightDreamComponent = lowLightDreamComponent;
}
@@ -111,7 +114,9 @@
mAmbientLightMode = ambientLightMode;
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
+ boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
+ mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
+ () -> mDreamManager.setSystemDreamComponent(
+ shouldEnterLowLight ? mLowLightDreamComponent : null));
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
new file mode 100644
index 0000000..874a2d5
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+public class LowLightTransitionCoordinator {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ public interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ Animator onBeforeEnterLowLight();
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ public interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ Animator onBeforeExitLowLight();
+ }
+
+ private LowLightEnterListener mLowLightEnterListener;
+ private LowLightExitListener mLowLightExitListener;
+
+ @Inject
+ public LowLightTransitionCoordinator() {
+ }
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
+ mLowLightEnterListener = lowLightEnterListener;
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
+ mLowLightExitListener = lowLightExitListener;
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light.
+ *
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ * @param callback callback that will be run after listeners complete.
+ */
+ void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
+ Animator animator = null;
+
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener.onBeforeEnterLowLight();
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener.onBeforeExitLowLight();
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ callback.run();
+ }
+ });
+ } else {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
index 91a170f..4b95d8c 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
@@ -21,7 +21,10 @@
import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -44,44 +47,52 @@
private DreamManager mDreamManager;
@Mock
+ private LowLightTransitionCoordinator mTransitionCoordinator;
+
+ @Mock
private ComponentName mDreamComponent;
+ LowLightDreamManager mLowLightDreamManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(1)).run();
+ return null;
+ }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
+ any(Runnable.class));
+
+ mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
+ mDreamComponent);
}
@Test
public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
}
@Test
public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
-
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
verify(mDreamManager).setSystemDreamComponent(null);
}
@Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
// Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager);
// Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null);
}
@@ -89,7 +100,7 @@
@Test
public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- null /*dream component*/);
+ mTransitionCoordinator, null /*dream component*/);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
new file mode 100644
index 0000000..81e1e33
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightTransitionCoordinatorTest {
+ @Mock
+ private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
+
+ @Mock
+ private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
+
+ @Mock
+ private Animator mAnimator;
+
+ @Captor
+ private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
+
+ @Mock
+ private Runnable mRunnable;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void onEnterCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verify(mEnterListener).onBeforeEnterLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void onExitCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightExitListener(mExitListener);
+
+ coordinator.notifyBeforeLowLightTransition(false, mRunnable);
+
+ verify(mExitListener).onBeforeExitLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void listenerNotCalledAfterRemoval() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+ coordinator.setLowLightEnterListener(null);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verifyZeroInteractions(mEnterListener);
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void runnableCalledAfterAnimationEnds() {
+ when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
+
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+ verifyZeroInteractions(mRunnable);
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
+ verify(mRunnable).run();
+ }
+}
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f17129c..1af60b2 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -108,8 +108,9 @@
std::move(data), std::string_view(fontPath.c_str(), fontPath.size()),
fontPtr, fontSize, ttcIndex, builder->axes);
if (minikinFont == nullptr) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "Failed to create internal object. maybe invalid font data.");
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Failed to create internal object. maybe invalid font data. filePath %s",
+ fontPath.c_str());
return 0;
}
uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index f85bdee..5f5e214 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -252,10 +252,10 @@
if (o == null || getClass() != o.getClass()) return false;
final AudioMix that = (AudioMix) o;
- return (this.mRouteFlags == that.mRouteFlags)
- && (this.mRule == that.mRule)
- && (this.mMixType == that.mMixType)
- && (this.mFormat == that.mFormat);
+ return (mRouteFlags == that.mRouteFlags)
+ && (mMixType == that.mMixType)
+ && Objects.equals(mRule, that.mRule)
+ && Objects.equals(mFormat, that.mFormat);
}
/** @hide */
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 440447e..ce97733 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -24,6 +24,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Objects;
@@ -50,7 +51,8 @@
mMixes = conf.mMixes;
}
- AudioPolicyConfig(ArrayList<AudioMix> mixes) {
+ @VisibleForTesting
+ public AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes;
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index d70e8b3..178a6d97 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -191,20 +191,13 @@
} else {
session = ContentRecordingSession.createTaskSession(launchCookie);
}
+ // Pass in the current session details, so they are guaranteed to only be set in WMS
+ // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up).
+ virtualDisplayConfig.setContentRecordingSession(session);
virtualDisplayConfig.setWindowManagerMirroringEnabled(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this,
virtualDisplayConfig.build(), callback, handler, windowContext);
- if (virtualDisplay == null) {
- // Since WM handling a new display and DM creating a new VirtualDisplay is async,
- // WM may have tried to start task recording and encountered an error that required
- // stopping recording entirely. The VirtualDisplay would then be null when the
- // MediaProjection is no longer active.
- return null;
- }
- session.setDisplayId(virtualDisplay.getDisplay().getDisplayId());
- // Successfully set up, so save the current session details.
- getProjectionService().setContentRecordingSession(session, mImpl);
return virtualDisplay;
} catch (RemoteException e) {
// Can not capture if WMS is not accessible, so bail out.
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index e9aa321..113c858 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -118,6 +118,9 @@
void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId);
+ // For TV Message
+ void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId);
+
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 82875e5..165a9dd 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -76,4 +76,7 @@
// For ad request
void requestAd(in AdRequest request);
void notifyAdBuffer(in AdBuffer buffer);
+
+ // For TV messages
+ void notifyTvMessage(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 465b617..8389706 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -78,6 +78,7 @@
private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
private static final int DO_TIME_SHIFT_SET_MODE = 30;
private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
+ private static final int DO_NOTIFY_TV_MESSAGE = 32;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -277,6 +278,11 @@
mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj);
break;
}
+ case DO_NOTIFY_TV_MESSAGE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -463,6 +469,11 @@
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer));
}
+ @Override
+ public void notifyTvMessage(String type, Bundle data) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+ }
+
private final class TvInputEventReceiver extends InputEventReceiver {
TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 8459538..55a753f 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -144,6 +144,13 @@
@StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION})
public @interface TvMessageType {}
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+ * identifies the stream on the TV input source for which the watermark event is relevant to.
+ */
+ public static final String TV_MESSAGE_KEY_STREAM_ID =
+ "android.media.tv.TvInputManager.stream_id";
+
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
static final int VIDEO_UNAVAILABLE_REASON_END = 18;
@@ -3221,6 +3228,17 @@
}
/**
+ * Sends TV messages to the service for testing purposes
+ */
+ public void notifyTvMessage(@NonNull @TvMessageType String type, @NonNull Bundle data) {
+ try {
+ mService.notifyTvMessage(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts TV program recording in the current recording session.
*
* @param programUri The URI for the TV program to record as a hint, built by
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4bc137d..9f40d70 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1491,7 +1491,18 @@
* {@code false} otherwise.
*/
public void onSetTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type,
- boolean enabled){
+ boolean enabled) {
+ }
+
+ /**
+ * Called when a TV message is received
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message
+ */
+ public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
+ @NonNull Bundle data) {
}
/**
@@ -2043,6 +2054,10 @@
onAdBuffer(buffer);
}
+ void onTvMessageReceived(String type, Bundle data) {
+ onTvMessage(type, data);
+ }
+
/**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 3ef61f2..5aeed1f 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -640,6 +641,20 @@
}
}
+
+ /**
+ * Sends TV messages to the session for testing purposes
+ *
+ * @hide
+ */
+ @TestApi
+ public void notifyTvMessage(@TvInputManager.TvMessageType @NonNull String type,
+ @NonNull Bundle data) {
+ if (mSession != null) {
+ mSession.notifyTvMessage(type, data);
+ }
+ }
+
/**
* Sets the callback to be invoked when the time shift position is changed.
*
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 63292ce..4624dfe 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -14,6 +14,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"guava",
+ "guava-android-testlib",
"hamcrest-library",
"platform-test-annotations",
],
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
new file mode 100644
index 0000000..bbca882
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
+
+import android.media.AudioFormat;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicyConfig;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for AudioMix.
+ *
+ * Run with "atest AudioMixUnitTests".
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioMixUnitTests {
+ private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(44000)
+ .setChannelMask(CHANNEL_OUT_STEREO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(CHANNEL_OUT_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+
+ @Test
+ public void testEquals() {
+ final EqualsTester equalsTester = new EqualsTester();
+
+ // --- Equality group 1
+ final AudioMix playbackAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix playbackAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(
+ playbackAudioMixWithSessionId42AndUid123,
+ playbackAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 2
+ final AudioMix recordingAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix recordingAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
+ recordingAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 3
+ final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(
+ AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
+
+ // --- Equality group 4
+ final AudioMix playbackAudioMixWithUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
+ writeToAndFromParcel(playbackAudioMixWithUid123));
+
+ // --- Equality group 5
+ final AudioMix playbackAudioMixWithUid42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 42).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
+ writeToAndFromParcel(playbackAudioMixWithUid42));
+
+ equalsTester.testEquals();
+ }
+
+ private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
+ AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
+ Parcel parcel = Parcel.obtain();
+ apc.writeToParcel(parcel, /*flags=*/0);
+ parcel.setDataPosition(0);
+ AudioMix unmarshalledMix =
+ AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
+ parcel.recycle();
+ return unmarshalledMix;
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index b842761..82e5a7f 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -36,17 +36,19 @@
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
- <!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
+ <!-- Title of the device association confirmation dialog for glasses. -->
+ <string name="confirmation_title_glasses">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage <strong><xliff:g id="device_name" example="Glasses">%2$s</xliff:g></strong>?</string>
+
<!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone:</string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -81,17 +83,13 @@
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
- <!-- TODO(b/256140614) To replace all nearby_device_streaming related strings with final versions -->
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="app_name" example="NearbyStreamer">%1$s</xliff:g></strong> to perform this action from your phone</string>
-
- <!-- Title of the helper dialog for NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=30]. -->
- <string name="helper_title_nearby_device_streaming">Cross-device services</string>
+ <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="NearbyDevice">%2$s</xliff:g> to stream content to nearby devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
<!-- ================= null profile ================= -->
@@ -161,7 +159,7 @@
<string name="permission_app_streaming">Apps</string>
<!-- Nearby_device_streaming permission will be granted to the corresponding profile [CHAR LIMIT=45] -->
- <string name="permission_nearby_device_streaming">Nearby Device Streaming</string>
+ <string name="permission_nearby_device_streaming">Streaming</string>
<!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_phone_summary">Can make and manage phone calls</string>
@@ -179,8 +177,7 @@
<string name="permission_calendar_summary">Can access your calendar</string>
<!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for microphone permission -->
- <string name="permission_microphone_summary">Can record audio using the microphone</string>
+ <string name="permission_microphone_summary">Can record audio</string>
<!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string>
@@ -195,7 +192,6 @@
<string name="permission_storage_summary"></string>
<!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for nearby devices' permission -->
- <string name="permission_nearby_device_streaming_summary">Stream content to a nearby device</string>
+ <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 918f9c6..8316f9d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -30,6 +30,7 @@
import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -571,6 +572,7 @@
final String deviceProfile = mRequest.getDeviceProfile();
final String profileName;
+ final String profileNameMulti;
final Spanned summary;
final Drawable profileIcon;
final int summaryResourceId;
@@ -580,6 +582,7 @@
}
profileName = getString(PROFILES_NAME.get(deviceProfile));
+ profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
@@ -590,7 +593,7 @@
}
final Spanned title = getHtmlFromResources(
- this, R.string.chooser_title, profileName, appLabel);
+ this, R.string.chooser_title, profileNameMulti, appLabel);
mTitle.setText(title);
mSummary.setText(summary);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index e3fd354..7aed139 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -59,7 +59,7 @@
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
- map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
TITLES = unmodifiableMap(map);
@@ -97,7 +97,7 @@
static {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
- map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
map.put(null, R.string.summary_generic);
MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
@@ -113,6 +113,16 @@
PROFILES_NAME = unmodifiableMap(map);
}
+ static final Map<String, Integer> PROFILES_NAME_MULTI;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic);
+ map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
+ map.put(null, R.string.profile_name_generic);
+
+ PROFILES_NAME_MULTI = unmodifiableMap(map);
+ }
+
static final Map<String, Integer> PROFILE_ICON;
static {
final Map<String, Integer> map = new ArrayMap<>();
@@ -133,7 +143,6 @@
SUPPORTED_PROFILES = unmodifiableSet(set);
}
-
static final Set<String> SUPPORTED_SELF_MANAGED_PROFILES;
static {
final Set<String> set = new ArraySet<>();
@@ -145,6 +154,4 @@
SUPPORTED_SELF_MANAGED_PROFILES = unmodifiableSet(set);
}
-
-
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index eae14a6..8f32dbb 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -21,6 +21,7 @@
import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
import static com.android.companiondevicemanager.Utils.getApplicationIcon;
+import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import android.annotation.Nullable;
@@ -105,9 +106,11 @@
final String packageName = request.getPackageName();
final CharSequence displayName = request.getDisplayName();
final int userId = request.getUserId();
+ final CharSequence appLabel;
try {
applicationIcon = getApplicationIcon(getContext(), packageName);
+ appLabel = getApplicationLabel(getContext(), packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
mListener.onShowHelperDialogFailed();
@@ -119,7 +122,7 @@
mAppIcon = view.findViewById(R.id.app_icon);
mButton = view.findViewById(R.id.btn_back);
- final Spanned title;
+ final CharSequence title;
final Spanned summary;
switch (deviceProfile) {
@@ -136,8 +139,7 @@
break;
case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
- title = getHtmlFromResources(getContext(),
- R.string.helper_title_nearby_device_streaming);
+ title = appLabel;
summary = getHtmlFromResources(
getContext(), R.string.helper_summary_nearby_device_streaming, title,
displayName);
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index ca30c53..e7de8b3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -22,6 +22,7 @@
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -36,6 +37,8 @@
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.logging.UIMetrics
+import com.android.internal.logging.UiEventLogger.UiEventEnum
/** One and only one of create or get state can be active at any given time. */
data class UiState(
@@ -44,7 +47,7 @@
val selectedEntry: BaseEntry? = null,
val providerActivityState: ProviderActivityState = ProviderActivityState.NOT_APPLICABLE,
val dialogState: DialogState = DialogState.ACTIVE,
- // True if the UI has one and onely one auto selectable entry. Its provider activiey will be
+ // True if the UI has one and only one auto selectable entry. Its provider activity will be
// launched immediately, and canceling it will cancel the whole UI flow.
val isAutoSelectFlow: Boolean = false,
)
@@ -56,6 +59,8 @@
var uiState by mutableStateOf(credManRepo.initState())
private set
+ var uiMetrics: UIMetrics = UIMetrics()
+
/**************************************************************************/
/***** Shared Callbacks *****/
/**************************************************************************/
@@ -76,6 +81,10 @@
fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) {
this.credManRepo = credManRepo
uiState = credManRepo.initState()
+
+ if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) {
+ this.uiMetrics.resetInstanceId()
+ }
}
fun launchProviderUi(
@@ -374,4 +383,9 @@
onInternalError()
}
}
+
+ @Composable
+ fun logUiEvent(uiEventEnum: UiEventEnum) {
+ this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo.appPackageName)
+ }
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e61633f..b5c8989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -60,8 +60,7 @@
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
// TODO: remove all !! checks
@@ -336,7 +335,7 @@
if (remoteEntry == null) {
return null
}
- val structuredRemoteEntry = RemoteCredentialEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
return RemoteEntryInfo(
providerId = providerId,
@@ -628,7 +627,7 @@
remoteEntry: Entry?,
): RemoteInfo? {
return if (remoteEntry != null) {
- val structuredRemoteEntry = RemoteCreateEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
RemoteInfo(
providerId = providerId,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index 82d6952..26aadd9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -32,8 +32,7 @@
import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import java.time.Instant
@@ -85,9 +84,7 @@
return Entry(
key,
subkey,
- RemoteCredentialEntry(pendingIntent, BeginGetPublicKeyCredentialOption(
- Bundle(), "id", "requestjson"
- )).slice
+ RemoteEntry(pendingIntent).slice
)
}
@@ -244,7 +241,7 @@
return Entry(
key,
subkey,
- RemoteCreateEntry(pendingIntent).slice
+ RemoteEntry(pendingIntent).slice
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index c0c29bb..1923542 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -102,8 +102,9 @@
SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine)
if (passwordValue != null) {
Row(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
) {
val visualTransformation = remember { PasswordVisualTransformation() }
val originalPassword by remember {
@@ -117,9 +118,13 @@
)
}
BodySmallText(
- text = displayedPassword.value, enforceOneLine = enforceOneLine)
+ text = displayedPassword.value,
+ // Apply weight to allow visibility button to render first so that
+ // it doesn't get squeezed out by a super long password.
+ modifier = Modifier.wrapContentSize().weight(1f, fill = false),
+ )
ToggleVisibilityButton(
- modifier = Modifier.padding(start = 12.dp, top = 5.dp).size(24.dp),
+ modifier = Modifier.padding(start = 12.dp).size(24.dp),
onToggle = {
if (it) {
displayedPassword.value = originalPassword
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9fe7899..5240777 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -67,6 +67,8 @@
import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
fun CreateCredentialScreen(
@@ -85,81 +87,93 @@
ProviderActivityState.NOT_APPLICABLE -> {
when (createCredentialUiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
- onConfirm = viewModel::createFlowOnConfirmIntro,
- onLearnMore = viewModel::createFlowOnLearnMore,
+ onConfirm = viewModel::createFlowOnConfirmIntro,
+ onLearnMore = viewModel::createFlowOnLearnMore,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasRemoteEntry = createCredentialUiState.remoteEntry != null,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasRemoteEntry = createCredentialUiState.remoteEntry != null,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- createOptionInfo =
- createCredentialUiState.activeEntry.activeEntryInfo
- as CreateOptionInfo,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ providerInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ createOptionInfo =
+ createCredentialUiState.activeEntry.activeEntryInfo
+ as CreateOptionInfo,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- isFromProviderSelection =
- createCredentialUiState.isFromProviderSelection!!,
- onBackProviderSelectionButtonSelected =
- viewModel::createFlowOnBackProviderSelectionButtonSelected,
- onBackCreationSelectionButtonSelected =
- viewModel::createFlowOnBackCreationSelectionButtonSelected,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ isFromProviderSelection =
+ createCredentialUiState.isFromProviderSelection!!,
+ onBackProviderSelectionButtonSelected =
+ viewModel::createFlowOnBackProviderSelectionButtonSelected,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> {
if (createCredentialUiState.activeEntry == null) {
viewModel.onIllegalUiState("Expect active entry to be non-null" +
- " upon default provider dialog.")
+ " upon default provider dialog.")
} else {
MoreOptionsRowIntroCard(
- selectedEntry = createCredentialUiState.activeEntry,
- onIllegalScreenState = viewModel::onIllegalUiState,
- onChangeDefaultSelected =
- viewModel::createFlowOnChangeDefaultSelected,
- onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ selectedEntry = createCredentialUiState.activeEntry,
+ onIllegalScreenState = viewModel::onIllegalUiState,
+ onChangeDefaultSelected =
+ viewModel::createFlowOnChangeDefaultSelected,
+ onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ onLog = { viewModel.logUiEvent(it) },
)
}
}
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- activeRemoteEntry =
- createCredentialUiState.activeEntry?.activeEntryInfo!!,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ activeRemoteEntry =
+ createCredentialUiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO ->
- MoreAboutPasskeysIntroCard(
+ CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected =
viewModel::createFlowOnBackPasskeyIntroButtonSelected,
- )
+ onLog = { viewModel.logUiEvent(it) },
+ )
}
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent
+ .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE)
}
ProviderActivityState.READY_TO_LAUNCH -> {
// Launch only once per providerActivityState change so that the provider
@@ -167,9 +181,14 @@
LaunchedEffect(viewModel.uiState.providerActivityState) {
viewModel.launchProviderUi(providerActivityLauncher)
}
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent
+ .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
}
ProviderActivityState.PENDING -> {
// Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING)
}
}
},
@@ -181,6 +200,7 @@
fun PasskeyIntroCard(
onConfirm: () -> Unit,
onLearnMore: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -245,6 +265,7 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
}
@Composable
@@ -256,6 +277,7 @@
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledProvidersSelected: () -> Unit,
onMoreOptionsSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
@@ -319,21 +341,23 @@
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
}
@Composable
fun MoreOptionsSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- hasDefaultProvider: Boolean,
- isFromProviderSelection: Boolean,
- onBackProviderSelectionButtonSelected: () -> Unit,
- onBackCreationSelectionButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledProvidersSelected: () -> Unit,
- onRemoteEntrySelected: (BaseEntry) -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+ hasDefaultProvider: Boolean,
+ isFromProviderSelection: Boolean,
+ onBackProviderSelectionButtonSelected: () -> Unit,
+ onBackCreationSelectionButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledProvidersSelected: () -> Unit,
+ onRemoteEntrySelected: (BaseEntry) -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(topAppBar = {
MoreOptionTopAppBar(
@@ -394,14 +418,16 @@
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION)
}
@Composable
fun MoreOptionsRowIntroCard(
- selectedEntry: ActiveEntry,
- onIllegalScreenState: (String) -> Unit,
- onChangeDefaultSelected: () -> Unit,
- onUseOnceSelected: () -> Unit,
+ selectedEntry: ActiveEntry,
+ onIllegalScreenState: (String) -> Unit,
+ onChangeDefaultSelected: () -> Unit,
+ onUseOnceSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
val entryInfo = selectedEntry.activeEntryInfo
if (entryInfo !is CreateOptionInfo) {
@@ -440,18 +466,20 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO)
}
@Composable
fun CreationSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- providerInfo: EnabledProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
- onMoreOptionsSelected: () -> Unit,
- hasDefaultProvider: Boolean,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onMoreOptionsSelected: () -> Unit,
+ hasDefaultProvider: Boolean,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -537,14 +565,16 @@
item { BodySmallText(text = createOptionInfo.footerDescription) }
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION)
}
@Composable
fun ExternalOnlySelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- activeRemoteEntry: BaseEntry,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: BaseEntry,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
@@ -572,11 +602,13 @@
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION)
}
@Composable
fun MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(
topAppBar = {
@@ -612,6 +644,7 @@
BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
}
@Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ab947ae..a9f994d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -295,13 +295,6 @@
)
}
}
- item {
- Divider(
- thickness = 1.dp,
- color = Color.LightGray,
- modifier = Modifier.padding(top = 16.dp)
- )
- }
// Manage sign-ins (action chips)
item {
ActionChips(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
new file mode 100644
index 0000000..daa42be
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "The create credential bottomsheet became visible on the screen.")
+ CREDMAN_CREATE_CRED_BOTTOMSHEET(1318),
+
+ @UiEvent(doc = "The provider activity is launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1319),
+
+ @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " +
+ "Contents Hidden.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING(1320),
+
+ @UiEvent(doc = "The provider activity is not active or ready launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1321),
+
+ @UiEvent(doc = "The passkey introduction card is visible on screen.")
+ CREDMAN_CREATE_CRED_PASSKEY_INTRO(1322),
+
+ @UiEvent(doc = "The provider selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_SELECTION(1323),
+
+ @UiEvent(doc = "The creation option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION(1324),
+
+ @UiEvent(doc = "The more option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION(1325),
+
+ @UiEvent(doc = "The more options row intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO(1326),
+
+ @UiEvent(doc = "The external only selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327),
+
+ @UiEvent(doc = "The more about passkeys intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328);
+
+ override fun getId(): Int {
+ return this.id
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
new file mode 100644
index 0000000..4351e84
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.logging
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLoggerImpl
+
+class UIMetrics() {
+ private val INSTANCE_ID_MAX = 1 shl 20
+ private val mUiEventLogger: UiEventLogger = UiEventLoggerImpl()
+ val mInstanceIdSequence: InstanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+
+ var mInstanceId: InstanceId = mInstanceIdSequence.newInstanceId()
+
+ fun resetInstanceId() {
+ this.mInstanceId = mInstanceIdSequence.newInstanceId()
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.log(event, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, packageName: String) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, instanceId: InstanceId, packageName: String) {
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 4ed7e19..10b004e 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -29,6 +29,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseIntArray;
@@ -36,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -57,6 +59,7 @@
private final SecureSettings mSecureSettings;
private String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
+ private SparseIntArray mDeviceStateDefaultRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
private String mLastSettingValue;
private List<SettableDeviceState> mSettableDeviceStates;
@@ -93,9 +96,7 @@
/** Returns true if device-state based rotation lock settings are enabled. */
public static boolean isDeviceStateRotationLockEnabled(Context context) {
return context.getResources()
- .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
- .length
- > 0;
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0;
}
private void listenForSettingsChange() {
@@ -228,6 +229,15 @@
try {
key = Integer.parseInt(values[i++]);
value = Integer.parseInt(values[i++]);
+ boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
+ == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ if (isPersistedValueIgnored != isDefaultValueIgnored) {
+ Log.w(TAG, "Conflict for ignored device state " + key
+ + ". Falling back on defaults");
+ fallbackOnDefaults();
+ return;
+ }
mDeviceStateRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing one of the saved settings", e);
@@ -276,6 +286,9 @@
}
private void persistSettingIfChanged(String newSettingValue) {
+ Log.v(TAG, "persistSettingIfChanged: "
+ + "last=" + mLastSettingValue + ", "
+ + "new=" + newSettingValue);
if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
return;
}
@@ -288,6 +301,8 @@
private void loadDefaults() {
mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
+ mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
+ mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -311,6 +326,7 @@
boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
+ mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
return;
@@ -318,6 +334,22 @@
}
}
+ /** Dumps internal state. */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("DeviceStateRotationLockSettingsManager");
+ pw.increaseIndent();
+ pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
+ mDeviceStateRotationLockDefaults));
+ pw.println("mDeviceStateDefaultRotationLockSettings: "
+ + mDeviceStateDefaultRotationLockSettings);
+ pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
+ pw.println("mDeviceStateRotationLockFallbackSettings: "
+ + mDeviceStateRotationLockFallbackSettings);
+ pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
+ pw.println("mLastSettingValue: " + mLastSettingValue);
+ pw.decreaseIndent();
+ }
+
/**
* Called when the persisted settings have changed, requiring a reinitialization of the
* in-memory map.
@@ -372,5 +404,13 @@
public int hashCode() {
return Objects.hash(mDeviceState, mIsSettable);
}
+
+ @Override
+ public String toString() {
+ return "SettableDeviceState{"
+ + "mDeviceState=" + mDeviceState
+ + ", mIsSettable=" + mIsSettable
+ + '}';
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 78df0f2..ca88f8d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.app.settings.SettingsEnums
import android.os.Bundle
// Defines the category of the log, for quick filter
@@ -31,20 +32,21 @@
}
// Defines the log events in Spa.
-enum class LogEvent {
+enum class LogEvent(val action: Int) {
// Page related events.
- PAGE_ENTER,
- PAGE_LEAVE,
+ PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
+ PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
// Entry related events.
- ENTRY_CLICK,
- ENTRY_SWITCH,
+ ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
+ ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
}
internal const val LOG_DATA_DISPLAY_NAME = "name"
-internal const val LOG_DATA_SESSION_NAME = "session"
internal const val LOG_DATA_SWITCH_STATUS = "switch"
+const val LOG_DATA_SESSION_NAME = "session"
+
/**
* The interface of logger in Spa
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
index 2c3c2e0..d8c35a3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -22,9 +22,11 @@
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+const val SESSION_UNKNOWN = "unknown"
const val SESSION_BROWSE = "browse"
const val SESSION_SEARCH = "search"
const val SESSION_SLICE = "slice"
+const val SESSION_EXTERNAL = "external"
const val KEY_DESTINATION = "spaActivityDestination"
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b92b3d6..8e7d36e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1648,4 +1648,7 @@
<item>Move right</item>
<item>Move up</item>
</string-array>
+
+ <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] -->
+ <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index fb06976..3ec5eba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -23,6 +23,7 @@
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -132,6 +133,11 @@
}
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public boolean isSwitchEnabled() {
+ return mEnableSwitch;
+ }
+
/**
* If admin is not null, disables the switch.
* Otherwise, keep it enabled.
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index 81006dd..0fa15eb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -33,7 +33,10 @@
import com.android.internal.R;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +48,8 @@
@RunWith(AndroidJUnit4.class)
public class DeviceStateRotationLockSettingsManagerTest {
+ @Rule public Expect mExpect = Expect.create();
+
@Mock private Context mMockContext;
@Mock private Resources mMockResources;
@@ -117,4 +122,40 @@
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
).inOrder();
}
+
+ @Test
+ public void persistedInvalidIgnoredState_returnsDefaults() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ // Here 2 has IGNORED, and in the defaults 1 has IGNORED.
+ persistSettings("0:2:2:0:1:2");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2);
+ }
+
+ @Test
+ public void persistedValidValues_returnsPersistedValues() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ persistSettings("0:2:1:0:2:1");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1);
+ }
+
+ private void persistSettings(String value) {
+ mFakeSecureSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ value,
+ UserHandle.USER_CURRENT);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index c9d840a..6a5535d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -97,5 +97,8 @@
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.CAMERA_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index c2a3ada..3fe12b3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -16,6 +16,7 @@
package android.provider.settings.validators;
+import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
@@ -215,5 +216,8 @@
VALIDATORS.put(System.UNREAD_NOTIFICATION_DOT_INDICATOR, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.AUTO_LAUNCH_MEDIA_CONTROLS, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCALE_PREFERENCES, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1a69208..d49627e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2835,6 +2835,15 @@
dumpSetting(s, p,
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
SystemSettingsProto.Notification.VIBRATION_INTENSITY);
+ dumpSetting(s, p,
+ Settings.System.CAMERA_FLASH_NOTIFICATION,
+ SystemSettingsProto.Notification.CAMERA_FLASH_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_FLASH_NOTIFICATION,
+ SystemSettingsProto.Notification.SCREEN_FLASH_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+ SystemSettingsProto.Notification.SCREEN_FLASH_NOTIFICATION_COLOR_GLOBAL);
// Settings.System.NOTIFICATIONS_USE_RING_VOLUME intentionally excluded since it's deprecated.
p.end(notificationToken);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 418011a..27c8cdf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3056,11 +3056,11 @@
final int key = makeKey(type, userId);
boolean success = false;
- boolean isNewSetting = false;
+ boolean wasUnsetNonPredefinedSetting = false;
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
- if (!settingsState.hasSetting(name)) {
- isNewSetting = true;
+ if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
+ wasUnsetNonPredefinedSetting = true;
}
success = settingsState.insertSettingLocked(name, value,
tag, makeDefault, forceNonSystemPackage, packageName,
@@ -3073,9 +3073,9 @@
if (forceNotify || success) {
notifyForSettingsChange(key, name);
- if (isNewSetting && !isSettingPreDefined(name, type)) {
- // Increment the generation number for all null settings because a new
- // non-predefined setting has been inserted
+ if (wasUnsetNonPredefinedSetting) {
+ // Increment the generation number for all non-predefined, unset settings,
+ // because a new non-predefined setting has been inserted
mGenerationRegistry.incrementGenerationForUnsetSettings(key);
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 17a94b86..296c2ae 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -419,7 +419,7 @@
internal val delegate: AnimationDelegate
init {
- delegate = AnimationDelegate(controller, callback, launchAnimator, listener)
+ delegate = AnimationDelegate(controller, callback, listener, launchAnimator)
}
@BinderThread
@@ -446,10 +446,10 @@
constructor(
private val controller: Controller,
private val callback: Callback,
- /** The animator to use to animate the window launch. */
- private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** Listener for animation lifecycle events. */
- private val listener: Listener? = null
+ private val listener: Listener? = null,
+ /** The animator to use to animate the window launch. */
+ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e65c327..8f90724 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -782,7 +782,7 @@
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
<integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
<!-- Duration in milliseconds of the y-translation animation when entering a dream -->
- <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
+ <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer>
<!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aba3fc4..0f2ce44 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -196,9 +196,6 @@
<!-- Increased height of a small notification in the status bar -->
<dimen name="notification_min_height_increased">146dp</dimen>
- <!-- Increased height of a collapsed media notification in the status bar -->
- <dimen name="notification_min_height_media">160dp</dimen>
-
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 54f933a..53a421d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -29,6 +29,7 @@
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.settings.SystemSettings
+import kotlin.math.roundToInt
/** The Dialog that contains a seekbar for changing the font size. */
class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
@@ -56,6 +57,16 @@
doneButton = requireViewById(com.android.internal.R.id.button1)
seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+ val labelArray = arrayOfNulls<String>(strEntryValues.size)
+ for (i in strEntryValues.indices) {
+ labelArray[i] =
+ context.resources.getString(
+ com.android.settingslib.R.string.font_scale_percentage,
+ (strEntryValues[i].toFloat() * 100).roundToInt()
+ )
+ }
+ seekBarWithIconButtonsView.setProgressStateLabels(labelArray)
+
seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 2501be9..e049ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -351,9 +351,20 @@
}
private void animateFromMinimized() {
- mIsMinimized = false;
- setExpandedView();
- animateIn();
+ if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+ mEnterAnimator.cancel();
+ }
+ mEnterAnimator = mView.getMinimizedFadeoutAnimation();
+ mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+ });
+ mEnterAnimator.start();
}
private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index f372bb4..28c57d3 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
@@ -286,6 +287,20 @@
mActionChips.clear();
}
+ Animator getMinimizedFadeoutAnimation() {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(mMinimizedPreview, "alpha", 1, 0);
+ anim.setDuration(66);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mMinimizedPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setAlpha(1);
+ }
+ });
+ return anim;
+ }
+
Animator getEnterAnimation() {
if (mAccessibilityManager.isEnabled()) {
mDismissButton.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 24f6296..de3a990 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -45,6 +45,7 @@
private SeekBar mSeekbar;
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+ private String[] mStateLabels = null;
public SeekBarWithIconButtonsView(Context context) {
this(context, null);
@@ -132,6 +133,30 @@
}
/**
+ * Stores the String array we would like to use for describing the state of seekbar progress
+ * and updates the state description with current progress.
+ *
+ * @param labels The state descriptions to be announced for each progress.
+ */
+ public void setProgressStateLabels(String[] labels) {
+ mStateLabels = labels;
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
+ }
+
+ /**
+ * Sets the state of seekbar based on current progress. The progress of seekbar is
+ * corresponding to the index of the string array. If the progress is larger than or equals
+ * to the length of the array, the state description is set to an empty string.
+ */
+ private void setSeekbarStateDescription() {
+ mSeekbar.setStateDescription(
+ (mSeekbar.getProgress() < mStateLabels.length)
+ ? mStateLabels[mSeekbar.getProgress()] : "");
+ }
+
+ /**
* Sets a onSeekbarChangeListener to the seekbar in the layout.
* We update the Start Icon and End Icon if needed when the seekbar progress is changed.
*/
@@ -173,6 +198,9 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ca1cef3..d0a92f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -43,7 +43,6 @@
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -131,9 +130,17 @@
}
}
- /** Starts the dream content and dream overlay entry animations. */
+ /**
+ * Starts the dream content and dream overlay entry animations.
+ *
+ * @param downwards if true, the entry animation translations downwards into position rather
+ * than upwards.
+ */
@JvmOverloads
- fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ fun startEntryAnimations(
+ downwards: Boolean,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
cancelAnimations()
mAnimator =
@@ -153,7 +160,7 @@
interpolator = Interpolators.LINEAR
),
translationYAnimator(
- from = mDreamInTranslationYDistance.toFloat(),
+ from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1),
to = 0f,
durationMs = mDreamInTranslationYDurationMs,
interpolator = Interpolators.EMPHASIZED_DECELERATE
@@ -167,6 +174,71 @@
}
}
+ /**
+ * Starts the dream content and dream overlay exit animations.
+ *
+ * This should only be used when the low light dream is entering, animations to/from other SysUI
+ * views is controlled by `transitionViewModel`.
+ */
+ // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is
+ // done.
+ @JvmOverloads
+ fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ translationYAnimator(
+ from = 0f,
+ to = -mDreamInTranslationYDistance.toFloat(),
+ durationMs = mDreamInTranslationYDurationMs,
+ delayMs = 0,
+ interpolator = Interpolators.EMPHASIZED
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_BOTTOM
+ )
+ .apply {
+ doOnEnd {
+ // The logical end of the animation is once the alpha and blur
+ // animations finish, end the animation so that any listeners are
+ // notified. The Y translation animation is much longer than all of
+ // the other animations due to how the spec is defined, but is not
+ // expected to run to completion.
+ mAnimator?.end()
+ }
+ },
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
+ return mAnimator as AnimatorSet
+ }
+
/** Starts the dream content and dream overlay exit animations. */
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
@@ -182,19 +254,6 @@
}
}
- /**
- * Ends the dream content and dream overlay animations, if they're currently running.
- *
- * @see [AnimatorSet.end]
- */
- fun endAnimations() {
- mAnimator =
- mAnimator?.let {
- it.end()
- null
- }
- }
-
private fun blurAnimator(
view: View,
fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 50cfb6a..4b478cd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -23,6 +23,7 @@
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
+import android.animation.Animator;
import android.content.res.Resources;
import android.os.Handler;
import android.util.MathUtils;
@@ -31,6 +32,7 @@
import androidx.annotation.NonNull;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,11 +56,14 @@
* View controller for {@link DreamOverlayContainerView}.
*/
@DreamOverlayComponent.DreamOverlayScope
-public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
+public class DreamOverlayContainerViewController extends
+ ViewController<DreamOverlayContainerView> implements
+ LowLightTransitionCoordinator.LowLightEnterListener {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -143,19 +148,18 @@
};
/**
- * If true, overlay entry animations should be skipped once.
- *
- * This is turned on when exiting low light and should be turned off once the entry animations
- * are skipped once.
+ * If {@code true}, the dream has just transitioned from the low light dream back to the user
+ * dream and we should play an entry animation where the overlay slides in downwards from the
+ * top instead of the typicla slide in upwards from the bottom.
*/
- private boolean mSkipEntryAnimations;
+ private boolean mExitingLowLight;
private final DreamOverlayStateController.Callback
mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@Override
public void onExitLowLight() {
- mSkipEntryAnimations = true;
+ mExitingLowLight = true;
}
};
@@ -165,6 +169,7 @@
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@@ -182,6 +187,7 @@
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
@@ -208,6 +214,7 @@
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
+ mLowLightTransitionCoordinator.setLowLightEnterListener(this);
}
@Override
@@ -219,14 +226,10 @@
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
- mDreamOverlayAnimationsController.startEntryAnimations();
-
- if (mSkipEntryAnimations) {
- // If we're transitioning from the low light dream back to the user dream, skip the
- // overlay animations and show immediately.
- mDreamOverlayAnimationsController.endAnimations();
- mSkipEntryAnimations = false;
- }
+ // If this is transitioning from the low light dream to the user dream, the overlay
+ // should translate in downwards instead of upwards.
+ mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
+ mExitingLowLight = false;
}
}
@@ -310,4 +313,12 @@
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
+
+ @Override
+ public Animator onBeforeEnterLowLight() {
+ // Return the animator so that the transition coordinator waits for the overlay exit
+ // animations to finish before entering low light, as otherwise the default DreamActivity
+ // animation plays immediately and there's no time for this animation to play.
+ return mDreamOverlayAnimationsController.startExitAnimations();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index a2e11b2..24e90f0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -22,14 +22,18 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.Collection;
import java.util.HashMap;
@@ -54,6 +58,8 @@
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ @VisibleForTesting
+ boolean mIsAnimationEnabled;
// Whether dream entry animations are finished.
private boolean mEntryAnimationsFinished = false;
@@ -64,7 +70,8 @@
ComplicationLayoutEngine layoutEngine,
DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
- @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+ @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel,
+ SecureSettings secureSettings) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
@@ -78,6 +85,10 @@
mDreamOverlayStateController.areEntryAnimationsFinished();
}
});
+
+ // Whether animations are enabled.
+ mIsAnimationEnabled = secureSettings.getFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f;
}
@Override
@@ -148,7 +159,7 @@
// Complications to be added before dream entry animations are finished are set
// to invisible and are animated in.
- if (!mEntryAnimationsFinished) {
+ if (!mEntryAnimationsFinished && mIsAnimationEnabled) {
view.setVisibility(View.INVISIBLE);
}
mComplications.put(id, viewHolder);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 95b37ac..9a6d90a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -71,8 +71,12 @@
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
releasedFlag(112, "notification_memory_monitor_enabled")
+ /**
+ * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
+ * enable it on release builds.
+ */
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
+ unreleasedFlag(119, "notification_memory_logging_enabled")
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
@@ -104,6 +108,10 @@
val NOTIFICATION_ANIMATE_BIG_PICTURE =
releasedFlag(120, "notification_animate_big_picture", teamfood = true)
+ @JvmField
+ val ANIMATED_NOTIFICATION_SHADE_INSETS =
+ unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -469,7 +477,7 @@
@Keep
@JvmField
val ENABLE_PIP_APP_ICON_OVERLAY =
- sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false)
+ sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
// 1200 - predictive back
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
new file mode 100644
index 0000000..beb725e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DeviceStateAutoRotationLog {
+}
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 ca1ed1f..d246b35e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -370,6 +370,16 @@
}
/**
+ * Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
+ */
+ @Provides
+ @SysUISingleton
+ @DeviceStateAutoRotationLog
+ public static LogBuffer provideDeviceStateAutoRotationLogBuffer(LogBufferFactory factory) {
+ return factory.create("DeviceStateAutoRotationLog", 100);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 680a8b6..67d3be4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -197,7 +197,6 @@
private val configListener =
object : ConfigurationController.ConfigurationListener {
- var lastOrientation = -1
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may
@@ -214,13 +213,6 @@
override fun onConfigChanged(newConfig: Configuration?) {
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
- val newOrientation = newConfig.orientation
- if (lastOrientation != newOrientation) {
- // The players actually depend on the orientation possibly, so we have to
- // recreate them (at least on large screen devices)
- lastOrientation = newOrientation
- updatePlayers(recreateMedia = true)
- }
}
override fun onUiModeChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 0788e61..b4724dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -154,9 +154,11 @@
return transitionLayout?.translationY ?: 0.0f
}
- /** A callback for RTL config changes */
+ /** A callback for config changes */
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
+ var lastOrientation = -1
+
override fun onConfigChanged(newConfig: Configuration?) {
// Because the TransitionLayout is not always attached (and calculates/caches layout
// results regardless of attach state), we have to force the layoutDirection of the
@@ -169,6 +171,13 @@
transitionLayout?.layoutDirection = layoutDirection
refreshState()
}
+ val newOrientation = newConfig.orientation
+ if (lastOrientation != newOrientation) {
+ // Layout dimensions are possibly changing, so we need to update them. (at
+ // least on large screen devices)
+ lastOrientation = newOrientation
+ loadLayoutForType(type)
+ }
}
}
}
@@ -195,13 +204,14 @@
* The expanded constraint set used to render a expanded player. If it is modified, make sure to
* call [refreshState]
*/
- val collapsedLayout = ConstraintSet()
-
+ var collapsedLayout = ConstraintSet()
+ @VisibleForTesting set
/**
* The expanded constraint set used to render a collapsed player. If it is modified, make sure
* to call [refreshState]
*/
- val expandedLayout = ConstraintSet()
+ var expandedLayout = ConstraintSet()
+ @VisibleForTesting set
/** Whether the guts are visible for the associated player. */
var isGutsVisible = false
@@ -483,7 +493,7 @@
*/
fun attach(transitionLayout: TransitionLayout, type: TYPE) =
traceSection("MediaViewController#attach") {
- updateMediaViewControllerType(type)
+ loadLayoutForType(type)
logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
@@ -641,7 +651,7 @@
return result
}
- private fun updateMediaViewControllerType(type: TYPE) {
+ private fun loadLayoutForType(type: TYPE) {
this.type = type
// These XML resources contain ConstraintSets that will apply to this player type's layout
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index a233cdc..006cedf 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -164,8 +164,6 @@
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Map;
@@ -175,6 +173,8 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Contains logic for a navigation bar view.
*/
@@ -251,6 +251,12 @@
private boolean mTransientShown;
private boolean mTransientShownFromGestureOnSystemBar;
+ /**
+ * This is to indicate whether the navigation bar button is forced visible. This is true
+ * when the setup wizard is on display. When that happens, the window frame should be provided
+ * as insets size directly.
+ */
+ private boolean mIsButtonForceVisible;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
private final LightBarController mMainLightBarController;
@@ -664,7 +670,8 @@
mView.setTouchHandler(mTouchHandler);
setNavBarMode(mNavBarMode);
mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
- mEdgeBackGestureHandler.setButtonForcedVisibleChangeCallback((forceVisible) -> {
+ mEdgeBackGestureHandler.setButtonForceVisibleChangeCallback((forceVisible) -> {
+ mIsButtonForceVisible = forceVisible;
repositionNavigationBar(mCurrentRotation);
});
mNavigationBarTransitions.addListener(this::onBarTransition);
@@ -1703,7 +1710,7 @@
private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
final InsetsFrameProvider navBarProvider;
- if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
+ if (insetsHeight != -1 && !mIsButtonForceVisible) {
navBarProvider = new InsetsFrameProvider(
ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight));
// Use window frame for IME.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index f335733..70040c7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -2,19 +2,15 @@
import android.content.Context
import android.content.res.Configuration
-import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.util.MathUtils.min
-import android.util.TypedValue
import android.view.View
-import androidx.appcompat.view.ContextThemeWrapper
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.internal.R.style.Theme_DeviceDefault
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -159,26 +155,21 @@
val isDeviceInNightTheme = resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
- val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
- .run {
- val typedValue = TypedValue()
- val a: TypedArray = obtainStyledAttributes(typedValue.data,
- intArrayOf(android.R.attr.colorControlActivated))
- val color = a.getColor(0, 0)
- a.recycle()
- color
+ arrowPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.textColorPrimary
}
+ )
- val colorPrimary =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
-
- arrowPaint.color = Utils.getColorAccentDefaultColor(context)
-
- arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
- colorPrimary
- } else {
- colorControlActivated
- }
+ arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.materialColorOnSecondary
+ } else {
+ com.android.internal.R.attr.colorAccentSecondary
+ }
+ )
}
inner class AnimatedFloat(
@@ -414,9 +405,9 @@
) {
horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
scale.updateRestingPosition(restingParams.scale)
- arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+ arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate)
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f409b23..80ed08c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -55,12 +55,12 @@
internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 160L
+private const val POP_ON_FLING_DELAY = 140L
internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -148,8 +148,6 @@
private var gestureSinceActionDown = 0L
private var gestureEntryTime = 0L
private var gestureActiveTime = 0L
- private var gestureInactiveOrEntryTime = 0L
- private var gestureArrowStrokeVisibleTime = 0L
private val elapsedTimeSinceActionDown
get() = SystemClock.uptimeMillis() - gestureSinceActionDown
@@ -441,34 +439,44 @@
updateArrowStateOnMove(yTranslation, xTranslation)
- when (currentState) {
- GestureState.ACTIVE -> {
- stretchActiveBackIndicator(fullScreenProgress(xTranslation))
- }
- GestureState.ENTRY -> {
- val progress = staticThresholdProgress(xTranslation)
- stretchEntryBackIndicator(progress)
-
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- mView.popArrowAlpha(0f, it.value)
- }
- }
- GestureState.INACTIVE -> {
- val progress = reactivationThresholdProgress(totalTouchDelta)
- stretchInactiveBackIndicator(progress)
-
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
- mView.popArrowAlpha(0f, it.value)
- }
- }
- else -> {}
+ val gestureProgress = when (currentState) {
+ GestureState.ACTIVE -> fullScreenProgress(xTranslation)
+ GestureState.ENTRY -> staticThresholdProgress(xTranslation)
+ GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta)
+ else -> null
}
- // set y translation
+ gestureProgress?.let {
+ when (currentState) {
+ GestureState.ACTIVE -> stretchActiveBackIndicator(gestureProgress)
+ GestureState.ENTRY -> stretchEntryBackIndicator(gestureProgress)
+ GestureState.INACTIVE -> stretchInactiveBackIndicator(gestureProgress)
+ else -> {}
+ }
+ }
+
+ setArrowStrokeAlpha(gestureProgress)
setVerticalTranslation(yOffset)
}
+ private fun setArrowStrokeAlpha(gestureProgress: Float?) {
+ val strokeAlphaProgress = when (currentState) {
+ GestureState.ENTRY -> gestureProgress
+ GestureState.INACTIVE -> gestureProgress
+ GestureState.ACTIVE,
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> 1f
+ GestureState.CANCELLED,
+ GestureState.GONE -> 0f
+ }
+
+ strokeAlphaProgress?.let { progress ->
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
+ }
+
private fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
@@ -599,7 +607,7 @@
private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
- val flingDistance = abs(endX - startX)
+ val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX
val isPastFlingVelocity = isDragAwayFromEdge(
velocityPxPerSecThreshold =
ViewConfiguration.get(context).scaledMinimumFlingVelocity)
@@ -764,7 +772,7 @@
GestureState.ENTRY,
GestureState.INACTIVE -> params.entryIndicator.arrowDimens
GestureState.ACTIVE -> params.activeIndicator.arrowDimens
- GestureState.FLUNG,
+ GestureState.FLUNG -> params.flungIndicator.arrowDimens
GestureState.COMMITTED -> params.committedIndicator.arrowDimens
GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
},
@@ -825,7 +833,6 @@
updateRestingArrowDimens()
gestureEntryTime = SystemClock.uptimeMillis()
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
@@ -857,7 +864,13 @@
}
GestureState.INACTIVE -> {
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+ // Typically entering INACTIVE means
+ // totalTouchDelta <= deactivationSwipeTriggerThreshold
+ // but because we can also independently enter this state
+ // if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold
+ // so that gesture progress in this state is consistent regardless of entry
+ totalTouchDelta = params.deactivationSwipeTriggerThreshold
val startingVelocity = convertVelocityToSpringStartingVelocity(
valueOnFastVelocity = -1.05f,
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 163b6fa..f28c275 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -178,7 +178,7 @@
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
- private Consumer<Boolean> mButtonForcedVisibleCallback;
+ private Consumer<Boolean> mButtonForceVisibleCallback;
private final PluginManager mPluginManager;
private final ProtoTracer mProtoTracer;
@@ -246,7 +246,7 @@
private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureBackEnabled;
- private boolean mIsButtonForcedVisible;
+ private boolean mIsButtonForceVisible;
private InputMonitor mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -413,8 +413,8 @@
mStateChangeCallback = callback;
}
- public void setButtonForcedVisibleChangeCallback(Consumer<Boolean> callback) {
- mButtonForcedVisibleCallback = callback;
+ public void setButtonForceVisibleChangeCallback(Consumer<Boolean> callback) {
+ mButtonForceVisibleCallback = callback;
}
public int getEdgeWidthLeft() {
@@ -429,14 +429,13 @@
Resources res = mNavigationModeController.getCurrentUserContext().getResources();
mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
- final boolean previousForcedVisible = mIsButtonForcedVisible;
- mIsButtonForcedVisible =
+ final boolean previousForceVisible = mIsButtonForceVisible;
+ mIsButtonForceVisible =
mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
- if (previousForcedVisible != mIsButtonForcedVisible
- && mButtonForcedVisibleCallback != null) {
- mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible);
+ if (previousForceVisible != mIsButtonForceVisible && mButtonForceVisibleCallback != null) {
+ mButtonForceVisibleCallback.accept(mIsButtonForceVisible);
}
- mIsBackGestureAllowed = !mIsButtonForcedVisible;
+ mIsBackGestureAllowed = !mIsButtonForceVisible;
final DisplayMetrics dm = res.getDisplayMetrics();
final float defaultGestureHeight = res.getDimension(
@@ -636,10 +635,6 @@
return mIsEnabled && mIsBackGestureAllowed;
}
- public boolean isButtonForcedVisible() {
- return mIsButtonForcedVisible;
- }
-
/**
* Update the PiP bounds, used for exclusion calculation.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 0c00022..d46333a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -9,8 +9,8 @@
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float = 0f,
- val height: Float = 0f,
+ val length: Float? = 0f,
+ val height: Float? = 0f,
val alpha: Float = 0f,
var alphaSpring: SpringForce? = null,
val heightSpring: SpringForce? = null,
@@ -139,17 +139,17 @@
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
- activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+ activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f)
arrowAngleInterpolator = entryWidthInterpolator
translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
- val showArrowOnProgressValue = .2f
+ val showArrowOnProgressValue = .23f
val showArrowOnProgressValueFactor = 1.05f
- val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+ val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f)
val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
@@ -178,7 +178,7 @@
height = getDimen(R.dimen.navigation_edge_entry_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
- alphaSpring = createSpring(900f, 1f),
+ alphaSpring = createSpring(1100f, 1f),
widthSpring = createSpring(450f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 0.5f),
@@ -232,7 +232,7 @@
getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
farCornerRadius =
getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
- widthSpring = createSpring(200f, 0.65f),
+ widthSpring = createSpring(250f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(200f, 1f),
edgeCornerRadiusSpring = createSpring(150f, 0.5f),
@@ -244,6 +244,8 @@
arrowDimens = activeIndicator.arrowDimens.copy(
lengthSpring = activeCommittedArrowLengthSpring,
heightSpring = activeCommittedArrowHeightSpring,
+ length = null,
+ height = null,
),
backgroundDimens = activeIndicator.backgroundDimens.copy(
alpha = 0f,
@@ -255,13 +257,15 @@
farCornerRadiusSpring = flungCommittedFarCornerSpring,
),
scale = 0.85f,
- scaleSpring = createSpring(650f, 1f),
+ scaleSpring = createSpring(1150f, 1f),
)
flungIndicator = committedIndicator.copy(
arrowDimens = committedIndicator.arrowDimens.copy(
lengthSpring = createSpring(850f, 0.46f),
heightSpring = createSpring(850f, 0.46f),
+ length = activeIndicator.arrowDimens.length,
+ height = activeIndicator.arrowDimens.height
),
backgroundDimens = committedIndicator.backgroundDimens.copy(
widthSpring = flungCommittedWidthSpring,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index c65f0aa..5b36e93 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,6 +22,9 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
@@ -54,8 +57,8 @@
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
private val optionalBubbles: Optional<Bubbles>,
- private val optionalUserManager: Optional<UserManager>,
- private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val userManager: UserManager,
+ private val keyguardManager: KeyguardManager,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val devicePolicyManager: DevicePolicyManager,
private val userTracker: UserTracker,
@@ -106,8 +109,6 @@
if (!isEnabled) return
val bubbles = optionalBubbles.getOrNull() ?: return
- val userManager = optionalUserManager.getOrNull() ?: return
- val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
@@ -140,12 +141,13 @@
logDebug { "onShowNoteTask - start: $info" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
+ // TODO(b/267634412, b/268351693): Should use `showOrHideAppBubbleAsUser`
bubbles.showOrHideAppBubble(intent)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
- context.startActivity(intent)
+ context.startActivityAsUser(intent, userTracker.userHandle)
eventLogger.logNoteTaskOpened(info)
logDebug { "onShowNoteTask - opened as activity: $info" }
}
@@ -198,12 +200,21 @@
}
private fun createNoteIntent(info: NoteTaskInfo): Intent =
- Intent(NoteTaskController.ACTION_CREATE_NOTE)
- .setPackage(info.packageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Intent(NoteTaskController.ACTION_CREATE_NOTE).apply {
+ setPackage(info.packageName)
+
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start it.
- .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+ putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+
+ addFlags(FLAG_ACTIVITY_NEW_TASK)
+ // We should ensure the note experience can be open both as a full screen (lock screen)
+ // and inside the app bubble (contextual). These additional flags will do that.
+ if (info.launchMode == NoteTaskLaunchMode.Activity) {
+ addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ }
+ }
private inline fun logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 0f75f95..7be491f 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -17,19 +17,19 @@
package com.android.systemui.notetask
import android.app.role.RoleManager
-import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.os.UserHandle
import android.util.Log
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
class NoteTaskInfoResolver
@Inject
constructor(
- private val context: Context,
private val roleManager: RoleManager,
private val packageManager: PackageManager,
+ private val userTracker: UserTracker,
) {
fun resolveInfo(
@@ -38,7 +38,7 @@
isKeyguardLocked: Boolean = false,
): NoteTaskInfo? {
// TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
- val user = context.user
+ val user = userTracker.userHandle
val packageName =
roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index ba8999c..6278c69 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -17,11 +17,7 @@
package com.android.systemui.notetask
import android.app.Activity
-import android.app.KeyguardManager
import android.app.role.RoleManager
-import android.content.Context
-import android.os.UserManager
-import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
@@ -32,7 +28,6 @@
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
@Module(includes = [NoteTaskQuickAffordanceModule::class])
@@ -55,15 +50,5 @@
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}
-
- @Provides
- fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
- return Optional.ofNullable(context.getSystemService())
- }
-
- @Provides
- fun provideOptionalUserManager(context: Context): Optional<UserManager> {
- return Optional.ofNullable(context.getSystemService())
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 7c2536d..d4854e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -328,7 +328,7 @@
if (listening) {
updateDefaultTileAndIcon();
refreshState();
- if (!mServiceManager.isActiveTile()) {
+ if (!mServiceManager.isActiveTile() || !isTileReady()) {
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index ead3b7b..0b4b7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -45,6 +45,7 @@
implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final float VELOCITY_DP_PER_MS = 1;
+ private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400;
private final SwipeDismissHandler mSwipeDismissHandler;
private final GestureDetector mSwipeDetector;
@@ -347,14 +348,18 @@
} else {
finalX = -1 * getBackgroundRight();
}
- float distance = Math.abs(finalX - startX);
+ float distance = Math.min(Math.abs(finalX - startX),
+ FloatingWindowUtil.dpToPx(mDisplayMetrics, MAXIMUM_DISMISS_DISTANCE_DP));
+ // ensure that view dismisses in the right direction (right in LTR, left in RTL)
+ float distanceVector = Math.copySign(distance, finalX - startX);
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(
+ startX, startX + distanceVector, animation.getAnimatedFraction());
mView.setTranslationX(translation);
mView.setAlpha(1 - animation.getAnimatedFraction());
});
- anim.setDuration((long) (distance / Math.abs(velocity)));
+ anim.setDuration((long) (Math.abs(distance / velocity)));
return anim;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b1987c1..ee9e54a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4720,8 +4720,13 @@
gesture possible. */
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
+ if (mTrackingPointer < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ } else {
+ mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer");
+ return false;
+ }
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c35c5c5..7755003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -93,6 +93,16 @@
@IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
public @interface VisibleState { }
+ /** Returns a human-readable string of {@link VisibleState}. */
+ public static String getVisibleStateString(@VisibleState int state) {
+ switch(state) {
+ case STATE_ICON: return "ICON";
+ case STATE_DOT: return "DOT";
+ case STATE_HIDDEN: return "HIDDEN";
+ default: return "UNKNOWN";
+ }
+ }
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -561,7 +571,8 @@
@Override
public String toString() {
return "StatusBarIconView("
- + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+ + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
+ + " visibleState=" + getVisibleStateString(getVisibleState())
+ " iconColor=#" + Integer.toHexString(mIconColor)
+ " notification=" + mNotification + ')';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
new file mode 100644
index 0000000..5ce1db2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArrayMap
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+
+/** A small coordinator which finds, stores, and applies the closest notification time. */
+@CoordinatorScope
+class GroupWhenCoordinator
+@Inject
+constructor(
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val systemClock: SystemClock
+) : Coordinator {
+
+ private val invalidator = object : Invalidator("GroupWhenCoordinator") {}
+ private val notificationGroupTimes = ArrayMap<GroupEntry, Long>()
+ private var cancelInvalidateListRunnable: Runnable? = null
+
+ private val invalidateListRunnable: Runnable = Runnable {
+ invalidator.invalidateList("future notification invalidation")
+ }
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
+ pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener)
+ pipeline.addPreRenderInvalidator(invalidator)
+ }
+
+ private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+ cancelListInvalidation()
+ notificationGroupTimes.clear()
+
+ val now = systemClock.currentTimeMillis()
+ var closestFutureTime = Long.MAX_VALUE
+ entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+ val whenMillis = calculateGroupNotificationTime(groupEntry, now)
+ notificationGroupTimes[groupEntry] = whenMillis
+ if (whenMillis > now) {
+ closestFutureTime = min(closestFutureTime, whenMillis)
+ }
+ }
+
+ if (closestFutureTime != Long.MAX_VALUE) {
+ cancelInvalidateListRunnable =
+ delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now)
+ }
+ }
+
+ private fun cancelListInvalidation() {
+ cancelInvalidateListRunnable?.run()
+ cancelInvalidateListRunnable = null
+ }
+
+ private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) {
+ notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen)
+ }
+
+ private fun calculateGroupNotificationTime(
+ groupEntry: GroupEntry,
+ currentTimeMillis: Long
+ ): Long {
+ var pastTime = Long.MIN_VALUE
+ var futureTime = Long.MAX_VALUE
+ groupEntry.children
+ .asSequence()
+ .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .forEach { time ->
+ val isInThePast = currentTimeMillis - time > 0
+ if (isInThePast) {
+ pastTime = max(pastTime, time)
+ } else {
+ futureTime = min(futureTime, time)
+ }
+ }
+
+ if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) {
+ return checkNotNull(groupEntry.summary).creationTime
+ }
+
+ return if (futureTime != Long.MAX_VALUE) futureTime else pastTime
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 8a82bca..6bb5b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -31,31 +31,32 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- notifPipelineFlags: NotifPipelineFlags,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator
+ notifPipelineFlags: NotifPipelineFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator
) : NotifCoordinators {
private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -82,6 +83,7 @@
mCoordinators.add(debugModeCoordinator)
mCoordinators.add(conversationCoordinator)
mCoordinators.add(groupCountCoordinator)
+ mCoordinators.add(groupWhenCoordinator)
mCoordinators.add(mediaCoordinator)
mCoordinators.add(rowAppearanceCoordinator)
mCoordinators.add(stackCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
index e2edc01..061ef9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
@@ -20,4 +20,7 @@
interface NotifGroupController {
/** Set the number of children that this group would have if not for the 8-child max */
fun setUntruncatedChildCount(untruncatedChildCount: Int)
+
+ /** Set the when value of notification group that reflects most important closest notification time */
+ fun setNotificationGroupWhen(whenMillis: Long)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 2affa77..6deaa23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -192,7 +192,6 @@
private int mMaxSmallHeightBeforeS;
private int mMaxSmallHeight;
private int mMaxSmallHeightLarge;
- private int mMaxSmallHeightMedia;
private int mMaxExpandedHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
@@ -853,6 +852,19 @@
}
/**
+ * @see NotificationChildrenContainer#setNotificationGroupWhen(long)
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
+ + " mIsSummaryWithChildren: false"
+ + " mChildrenContainer has not been inflated yet.");
+ }
+ }
+
+ /**
* Called after children have been attached to set the expansion states
*/
public void resetChildSystemExpandedStates() {
@@ -1774,8 +1786,6 @@
R.dimen.notification_min_height);
mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mMaxSmallHeightMedia = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_min_height_media);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 2dda6fd..dfc80fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -349,6 +349,15 @@
}
@Override
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mView.isSummaryWithChildren()) {
+ mView.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row");
+ }
+ }
+
+ @Override
public void setSystemExpanded(boolean systemExpanded) {
mView.setSystemExpanded(systemExpanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 1f664cb..9a777ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -27,6 +27,7 @@
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.widget.DateTimeView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -344,6 +345,21 @@
mTransformationHelper.setVisible(visible);
}
+ /***
+ * Set Notification when value
+ * @param whenMillis
+ */
+ public void setNotificationWhen(long whenMillis) {
+ if (mNotificationHeader == null) {
+ return;
+ }
+
+ final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time);
+
+ if (timeView instanceof DateTimeView) {
+ ((DateTimeView) timeView).setTime(whenMillis);
+ }
+ }
protected void addTransformedViews(View... views) {
for (View view : views) {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 9b93d7b..40f55bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -296,6 +296,19 @@
}
/**
+ * Set the notification time in the group so that the view can show the latest event in the UI
+ * appropriately.
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.setNotificationWhen(whenMillis);
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis);
+ }
+ }
+
+ /**
* Add a child notification to this view.
*
* @param row the row to add
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e09b94b..8d782e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -67,6 +67,7 @@
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
@@ -199,6 +200,7 @@
private final boolean mDebugRemoveAnimation;
private final boolean mSimplifiedAppearFraction;
private final boolean mUseRoundnessSourceTypes;
+ private boolean mAnimatedInsets;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -207,7 +209,8 @@
private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
private int mBottomPadding;
- private int mBottomInset = 0;
+ @VisibleForTesting
+ int mBottomInset = 0;
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
@@ -388,9 +391,33 @@
}
}
};
+
private boolean mPulsing;
private boolean mScrollable;
private View mForcedScroll;
+ private boolean mIsInsetAnimationRunning;
+
+ private final WindowInsetsAnimation.Callback mInsetsCallback =
+ new WindowInsetsAnimation.Callback(
+ WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+
+ @Override
+ public void onPrepare(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = true;
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets windowInsets,
+ List<WindowInsetsAnimation> list) {
+ updateBottomInset(windowInsets);
+ return windowInsets;
+ }
+
+ @Override
+ public void onEnd(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = false;
+ }
+ };
/**
* @see #setHideAmount(float, float)
@@ -584,6 +611,7 @@
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
+ setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -622,6 +650,9 @@
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ if (mAnimatedInsets) {
+ setWindowInsetsAnimationCallback(mInsetsCallback);
+ }
}
/**
@@ -690,6 +721,11 @@
}
@VisibleForTesting
+ void setAnimatedInsetsEnabled(boolean enabled) {
+ mAnimatedInsets = enabled;
+ }
+
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
if (mFooterView == null) {
@@ -1781,7 +1817,11 @@
return;
}
mForcedScroll = v;
- scrollTo(v);
+ if (mAnimatedInsets) {
+ updateForcedScroll();
+ } else {
+ scrollTo(v);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -1813,26 +1853,46 @@
+ ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
}
+ private void updateBottomInset(WindowInsets windowInsets) {
+ mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+
+ if (mForcedScroll != null) {
+ updateForcedScroll();
+ }
+
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ setOwnScrollY(range);
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (!mAnimatedInsets) {
+ mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ }
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
mWaterfallTopInset = cutout.getWaterfallInsets().top;
}
-
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
+ if (mAnimatedInsets && !mIsInsetAnimationRunning) {
+ // update bottom inset e.g. after rotation
+ updateBottomInset(insets);
+ }
+ if (!mAnimatedInsets) {
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ // HACK: We're repeatedly getting staggered insets here while the IME is
+ // animating away. To work around that we'll wait until things have settled.
+ removeCallbacks(mReclamp);
+ postDelayed(mReclamp, 50);
+ } else if (mForcedScroll != null) {
+ // The scroll was requested before we got the actual inset - in case we need
+ // to scroll up some more do so now.
+ scrollTo(mForcedScroll);
+ }
}
return insets;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c72eb05..39b5b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
@@ -288,10 +289,14 @@
* @param mobileContext possibly mcc/mnc overridden mobile context
* @param subId the subscriptionId for this mobile view
*/
- public void addModernMobileView(Context mobileContext, int subId) {
+ public void addModernMobileView(
+ Context mobileContext,
+ MobileViewLogger mobileViewLogger,
+ int subId) {
Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext,
+ mobileViewLogger,
"mobile",
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 11863627..04cc8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -569,7 +569,10 @@
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
- mDemoStatusIcons.addModernMobileView(mContext, subId);
+ mDemoStatusIcons.addModernMobileView(
+ mContext,
+ mMobileIconsViewModel.getLogger(),
+ subId);
}
return view;
@@ -601,6 +604,7 @@
return ModernStatusBarMobileView
.constructAndBind(
mobileContext,
+ mMobileIconsViewModel.getLogger(),
slot,
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index f6c0da8..833cb93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -79,6 +79,18 @@
private @IconType int mType = TYPE_ICON;
private int mTag = 0;
+ /** Returns a human-readable string representing the given type. */
+ public static String getTypeString(@IconType int type) {
+ switch(type) {
+ case TYPE_ICON: return "ICON";
+ case TYPE_WIFI: return "WIFI_OLD";
+ case TYPE_MOBILE: return "MOBILE_OLD";
+ case TYPE_MOBILE_NEW: return "MOBILE_NEW";
+ case TYPE_WIFI_NEW: return "WIFI_NEW";
+ default: return "UNKNOWN";
+ }
+ }
+
private StatusBarIconHolder() {
}
@@ -230,4 +242,11 @@
public int getTag() {
return mTag;
}
+
+ @Override
+ public String toString() {
+ return "StatusBarIconHolder(type=" + getTypeString(mType)
+ + " tag=" + getTag()
+ + " visible=" + isVisible() + ")";
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index 8800b05..565481a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/** A class holding the list of all the system icons that could be shown in the status bar. */
public class StatusBarIconList {
@@ -302,7 +303,7 @@
@Override
public String toString() {
- return String.format("(%s) %s", mName, subSlotsString());
+ return String.format("(%s) holder=%s %s", mName, mHolder, subSlotsString());
}
private String subSlotsString() {
@@ -310,7 +311,10 @@
return "";
}
- return "" + mSubSlots.size() + " subSlots";
+ return "| " + mSubSlots.size() + " subSlots: "
+ + mSubSlots.stream()
+ .map(StatusBarIconHolder::toString)
+ .collect(Collectors.joining("|"));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 79c0984..d30d0e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
import kotlin.math.max
@@ -29,9 +30,13 @@
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
private val progressProvider: ScopedUnfoldTransitionProgressProvider,
+ private val currentActivityTypeProvider: CurrentActivityTypeProvider,
windowManager: WindowManager
) {
+ // Whether we're on home activity. Updated only when the animation starts.
+ private var isOnHomeActivity: Boolean? = null
+
private val transitionListener = TransitionListener()
private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
windowManager,
@@ -60,6 +65,10 @@
}
private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ isOnHomeActivity = currentActivityTypeProvider.isHomeActivity
+ }
+
override fun onTransitionProgress(progress: Float) {
moveFromCenterAnimator.onTransitionProgress(progress)
}
@@ -68,11 +77,23 @@
// Reset translations when transition is stopped/cancelled
// (e.g. the transition could be cancelled mid-way when rotating the screen)
moveFromCenterAnimator.onTransitionProgress(1f)
+ isOnHomeActivity = null
}
}
- private class StatusBarIconsAlphaProvider : AlphaProvider {
+
+ /**
+ * In certain cases, an alpha is applied based on the progress.
+ *
+ * This mainly happens to hide the statusbar during the unfold animation while on apps, as the
+ * bounds of the app "collapse" to the center, but the statusbar doesn't.
+ * While on launcher, this alpha is not applied.
+ */
+ private inner class StatusBarIconsAlphaProvider : AlphaProvider {
override fun getAlpha(progress: Float): Float {
+ if (isOnHomeActivity == true) {
+ return 1.0f
+ }
return max(
0f,
(progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt
new file mode 100644
index 0000000..e594a8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class MobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 4464751..adfea80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -148,5 +148,19 @@
fun provideMobileInputLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("MobileInputLog", 100)
}
+
+ @Provides
+ @SysUISingleton
+ @MobileViewLog
+ fun provideMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MobileViewLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
+ @VerboseMobileViewLog
+ fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("VerboseMobileViewLog", 100)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt
new file mode 100644
index 0000000..b987898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for **verbose** changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class VerboseMobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 3cbd2b7..159f689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
@@ -133,24 +133,6 @@
)
}
- fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter updated internally: $str1" },
- )
- }
-
- fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
- )
- }
-
fun logCarrierConfigChanged(subId: Int) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index bb3b9b2..efdce06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -30,8 +30,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 96b96f1..e182bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -36,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -47,7 +48,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 53a208c..f97e41c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -45,11 +45,11 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index da63ab1..075e6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -22,7 +22,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.io.PrintWriter
import javax.inject.Inject
@@ -55,17 +54,14 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
- private val logger: MobileInputLogger,
+ private val logger: MobileViewLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions
- .mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
- .distinctUntilChanged()
- .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ interactor.filteredSubscriptions.mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -75,7 +71,10 @@
* NOTE: this should go away as the view presenter learns more about this data pipeline
*/
private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ mobileSubIds
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
new file mode 100644
index 0000000..90dff23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui
+
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Logs for changes with the new mobile views. */
+@SysUISingleton
+class MobileViewLogger
+@Inject
+constructor(
+ @MobileViewLog private val buffer: LogBuffer,
+ dumpManager: DumpManager,
+) : Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(this)
+ }
+
+ private val collectionStatuses = mutableMapOf<String, Boolean>()
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
+ fun logNewViewBinding(view: View, viewModel: LocationBasedMobileViewModel) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStarted(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = true
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStopped(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = false
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Collection statuses per view:---")
+ collectionStatuses.forEach { viewId, isCollecting ->
+ pw.println("viewId=$viewId, isCollecting=$isCollecting")
+ }
+ }
+
+ companion object {
+ fun Any.getIdForLogging(): String {
+ // The identityHashCode is guaranteed to be constant for the lifetime of the object.
+ return Integer.toHexString(System.identityHashCode(this))
+ }
+ }
+}
+
+private const val TAG = "MobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
new file mode 100644
index 0000000..f67bc8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui
+
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import javax.inject.Inject
+
+/**
+ * Logs for **verbose** changes with the new mobile views.
+ *
+ * This is a hopefully temporary log until we resolve some open bugs (b/267236367, b/269565345,
+ * b/270300839).
+ */
+@SysUISingleton
+class VerboseMobileViewLogger
+@Inject
+constructor(
+ @VerboseMobileViewLog private val buffer: LogBuffer,
+) {
+ fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ int2 = icon.level
+ bool1 = icon.showExclamationMark
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new signal icon: " +
+ "level=$int2 showExclamation=$bool1"
+ },
+ )
+ }
+
+ fun logBinderReceivedNetworkTypeIcon(parentView: View, subId: Int, icon: Icon.Resource?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ bool1 = icon != null
+ int2 = icon?.res ?: -1
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new network type icon: " +
+ if (bool1) "resId=$int2" else "null"
+ },
+ )
+ }
+}
+
+private const val TAG = "VerboseMobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index db585e6..5b7d45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -36,8 +36,10 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -48,6 +50,7 @@
fun bind(
view: ViewGroup,
viewModel: LocationBasedMobileViewModel,
+ logger: MobileViewLogger,
): ModernStatusBarViewBinding {
val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
val activityContainer = view.requireViewById<View>(R.id.inout_container)
@@ -70,8 +73,13 @@
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ logger.logCollectionStarted(view, viewModel)
+ isCollecting = true
+
launch {
visibilityState.collect { state ->
when (state) {
@@ -96,6 +104,11 @@
// Set the icon for the triangle
launch {
viewModel.icon.distinctUntilChanged().collect { icon ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ icon,
+ )
mobileDrawable.level =
SignalDrawable.getState(
icon.level,
@@ -114,6 +127,11 @@
// Set the network type icon
launch {
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+ view,
+ viewModel.subscriptionId,
+ dataTypeId,
+ )
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
}
@@ -150,6 +168,13 @@
}
launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ logger.logCollectionStopped(view, viewModel)
+ }
}
}
@@ -175,6 +200,10 @@
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index ed9a188..4144293d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -20,6 +20,8 @@
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
@@ -31,6 +33,15 @@
var subId: Int = -1
+ override fun toString(): String {
+ return "ModernStatusBarMobileView(" +
+ "slot='$slot', " +
+ "subId=$subId, " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
@@ -40,6 +51,7 @@
@JvmStatic
fun constructAndBind(
context: Context,
+ logger: MobileViewLogger,
slot: String,
viewModel: LocationBasedMobileViewModel,
): ModernStatusBarMobileView {
@@ -48,7 +60,8 @@
as ModernStatusBarMobileView)
.also {
it.subId = viewModel.subscriptionId
- it.initView(slot) { MobileIconBinder.bind(it, viewModel) }
+ it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) }
+ logger.logNewViewBinding(it, viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index 8e103f7..f775940 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -26,11 +27,15 @@
*
* @param commonImpl for convenience, this class wraps a base interface that can provides all of the
* common implementations between locations. See [MobileIconViewModel]
+ * @property locationName the name of the location of this VM, used for logging.
+ * @property verboseLogger an optional logger to log extremely verbose view updates.
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
+ val locationName: String,
+ val verboseLogger: VerboseMobileViewLogger?,
) : MobileIconViewModelCommon by commonImpl {
val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
@@ -45,11 +50,16 @@
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
StatusBarLocation.HOME ->
- HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ HomeMobileIconViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ verboseMobileViewLogger,
+ )
StatusBarLocation.KEYGUARD ->
KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -60,20 +70,41 @@
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.CYAN,
+ locationName = "Home",
+ verboseMobileViewLogger,
+ )
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.GREEN,
+ locationName = "QS",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.MAGENTA,
+ locationName = "Keyguard",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 0496278..dbb534b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -49,7 +49,7 @@
val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- val networkTypeIcon: Flow<Icon?>
+ val networkTypeIcon: Flow<Icon.Resource?>
val activityInVisible: Flow<Boolean>
val activityOutVisible: Flow<Boolean>
val activityContainerVisible: Flow<Boolean>
@@ -161,7 +161,7 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val networkTypeIcon: Flow<Icon?> =
+ override val networkTypeIcon: Flow<Icon.Resource?> =
combine(
iconInteractor.networkTypeIconGroup,
showNetworkTypeIcon,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 8cb52af..2b90065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
@@ -39,6 +41,8 @@
@Inject
constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>,
+ val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -66,6 +70,7 @@
return LocationBasedMobileViewModel.viewModelForLocation(
common,
statusBarPipelineFlags,
+ verboseLogger,
location,
)
}
@@ -79,6 +84,8 @@
class Factory
@Inject
constructor(
+ private val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -88,6 +95,8 @@
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index f67876b..81f8683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -37,4 +37,7 @@
/** Notifies that the decor tint has been updated (used only for the dot). */
fun onDecorTintChanged(newTint: Int)
+
+ /** Returns true if the binding between the view and view-model is currently collecting. */
+ fun isCollecting(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index b1e2812..1a13404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -36,7 +36,7 @@
BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
- private lateinit var binding: ModernStatusBarViewBinding
+ internal lateinit var binding: ModernStatusBarViewBinding
@StatusBarIconView.VisibleState
private var iconVisibleState: Int = STATE_HIDDEN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 2aff12c..9e8c814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -74,8 +75,12 @@
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ isCollecting = true
+
launch {
visibilityState.collect { visibilityState ->
groupView.isVisible = visibilityState == STATE_ICON
@@ -127,6 +132,12 @@
airplaneSpacer.isVisible = visible
}
}
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ }
}
}
@@ -152,6 +163,10 @@
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 7a73486..f23e102 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -21,6 +21,7 @@
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
@@ -33,6 +34,15 @@
context: Context,
attrs: AttributeSet?,
) : ModernStatusBarView(context, attrs) {
+
+ override fun toString(): String {
+ return "ModernStatusBarWifiView(" +
+ "slot='$slot', " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
@@ -45,12 +55,9 @@
slot: String,
wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
- return (
- LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
- as ModernStatusBarWifiView
- ).also {
- it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
- }
+ return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
+ as ModernStatusBarWifiView)
+ .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 7acdaff..01fabcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -22,13 +22,18 @@
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Trace;
-import android.util.Log;
+import android.util.IndentingPrintWriter;
+
+import androidx.annotation.NonNull;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -39,19 +44,19 @@
*/
@SysUISingleton
public final class DeviceStateRotationLockSettingController
- implements Listenable, RotationLockController.RotationLockControllerCallback {
-
- private static final String TAG = "DSRotateLockSettingCon";
+ implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable {
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
+ private final DeviceStateRotationLockSettingControllerLogger mLogger;
// On registration for DeviceStateCallback, we will receive a callback with the current state
// and this will be initialized.
private int mDeviceState = -1;
- @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+ @Nullable
+ private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
mDeviceStateRotationLockSettingsListener;
@@ -60,21 +65,27 @@
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
- DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
+ DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager,
+ DeviceStateRotationLockSettingControllerLogger logger,
+ DumpManager dumpManager) {
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
+ mLogger = logger;
+ dumpManager.registerDumpable(this);
}
@Override
public void setListening(boolean listening) {
+ mLogger.logListeningChange(listening);
if (listening) {
// Note that this is called once with the initial state of the device, even if there
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
- mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
+ mDeviceStateRotationLockSettingsListener = () ->
+ readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
mDeviceStateRotationLockSettingsManager.registerListener(
mDeviceStateRotationLockSettingsListener);
} else {
@@ -89,35 +100,28 @@
}
@Override
- public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
- if (mDeviceState == -1) {
- Log.wtf(TAG, "Device state was not initialized.");
+ public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) {
+ int deviceState = mDeviceState;
+ boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager
+ .isRotationLocked(deviceState);
+ mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked);
+ if (deviceState == -1) {
return;
}
-
- if (rotationLocked
- == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
- Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
+ if (newRotationLocked == currentRotationLocked) {
return;
}
-
- saveNewRotationLockSetting(rotationLocked);
+ saveNewRotationLockSetting(newRotationLocked);
}
private void saveNewRotationLockSetting(boolean isRotationLocked) {
- Log.v(
- TAG,
- "saveNewRotationLockSetting [state="
- + mDeviceState
- + "] [isRotationLocked="
- + isRotationLocked
- + "]");
-
- mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
+ int deviceState = mDeviceState;
+ mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState);
+ mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
}
private void updateDeviceState(int state) {
- Log.v(TAG, "updateDeviceState [state=" + state + "]");
+ mLogger.logUpdateDeviceState(mDeviceState, state);
if (Trace.isEnabled()) {
Trace.traceBegin(
Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
@@ -127,22 +131,26 @@
return;
}
- readPersistedSetting(state);
+ readPersistedSetting("updateDeviceState", state);
} finally {
Trace.endSection();
}
}
- private void readPersistedSetting(int state) {
+ private void readPersistedSetting(String caller, int state) {
int rotationLockSetting =
mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
+ boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ boolean isLocked = mRotationPolicyWrapper.isRotationLocked();
+
+ mLogger.readPersistedSetting(caller, state, rotationLockSetting, shouldBeLocked, isLocked);
+
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
// This should not happen. Device states that have an ignored setting, should also
// specify a fallback device state which is not ignored.
// We won't handle this device state. The same rotation lock setting as before should
// apply and any changes to the rotation lock setting will be written for the previous
// valid device state.
- Log.w(TAG, "Missing fallback. Ignoring new device state: " + state);
return;
}
@@ -150,9 +158,18 @@
mDeviceState = state;
// Update the rotation policy, if needed, for this new device state
- boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
- if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
- mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
+ if (shouldBeLocked != isLocked) {
+ mRotationPolicyWrapper.setRotationLock(shouldBeLocked);
}
}
+
+ @Override
+ public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
+ mDeviceStateRotationLockSettingsManager.dump(pw);
+ pw.println("DeviceStateRotationLockSettingController");
+ pw.increaseIndent();
+ pw.println("mDeviceState: " + mDeviceState);
+ pw.decreaseIndent();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
new file mode 100644
index 0000000..aa502bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
+import com.android.internal.R
+import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import javax.inject.Inject
+
+class DeviceStateRotationLockSettingControllerLogger
+@Inject
+constructor(@DeviceStateAutoRotationLog private val logBuffer: LogBuffer, context: Context) {
+
+ private val foldedStates = context.resources.getIntArray(R.array.config_foldedDeviceStates)
+ private val halfFoldedStates =
+ context.resources.getIntArray(R.array.config_halfFoldedDeviceStates)
+ private val unfoldedStates = context.resources.getIntArray(R.array.config_openDeviceStates)
+
+ fun logListeningChange(listening: Boolean) {
+ logBuffer.log(TAG, VERBOSE, { bool1 = listening }, { "setListening: $bool1" })
+ }
+
+ fun logRotationLockStateChanged(
+ state: Int,
+ newRotationLocked: Boolean,
+ currentRotationLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = state
+ bool1 = newRotationLocked
+ bool2 = currentRotationLocked
+ },
+ {
+ "onRotationLockStateChanged: " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "newRotationLocked=$bool1, " +
+ "currentRotationLocked=$bool2"
+ }
+ )
+ }
+
+ fun logSaveNewRotationLockSetting(isRotationLocked: Boolean, state: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ bool1 = isRotationLocked
+ int1 = state
+ },
+ { "saveNewRotationLockSetting: isRotationLocked=$bool1, state=$int1" }
+ )
+ }
+
+ fun logUpdateDeviceState(currentState: Int, newState: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = currentState
+ int2 = newState
+ },
+ {
+ "updateDeviceState: " +
+ "current=$int1 [${int1.toDevicePostureString()}], " +
+ "new=$int2 [${int2.toDevicePostureString()}]"
+ }
+ )
+ }
+
+ fun readPersistedSetting(
+ caller: String,
+ state: Int,
+ rotationLockSetting: Int,
+ shouldBeLocked: Boolean,
+ isLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = caller
+ int1 = state
+ int2 = rotationLockSetting
+ bool1 = shouldBeLocked
+ bool2 = isLocked
+ },
+ {
+ "readPersistedSetting: " +
+ "caller=$str1, " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "rotationLockSettingForState: ${int2.toRotationLockSettingString()}, " +
+ "shouldBeLocked=$bool1, " +
+ "isLocked=$bool2"
+ }
+ )
+ }
+
+ private fun Int.toDevicePostureString(): String {
+ return when (this) {
+ in foldedStates -> "Folded"
+ in unfoldedStates -> "Unfolded"
+ in halfFoldedStates -> "Half-Folded"
+ -1 -> "Uninitialized"
+ else -> "Unknown"
+ }
+ }
+}
+
+private fun Int.toRotationLockSettingString(): String {
+ return when (this) {
+ DEVICE_STATE_ROTATION_LOCK_IGNORED -> "IGNORED"
+ DEVICE_STATE_ROTATION_LOCK_LOCKED -> "LOCKED"
+ DEVICE_STATE_ROTATION_LOCK_UNLOCKED -> "UNLOCKED"
+ else -> "Unknown"
+ }
+}
+
+private const val TAG = "DSRotateLockSettingCon"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index 3503902..9d16185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -45,8 +45,8 @@
public class BroadcastDialogTest extends SysuiTestCase {
private static final String CURRENT_BROADCAST_APP = "Music";
- private static final String SWITCH_APP = "Files by Google";
- private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
+ private static final String SWITCH_APP = "System UI";
+ private static final String TEST_PACKAGE = "com.android.systemui";
private BroadcastDialog mBroadcastDialog;
private View mDialogView;
private TextView mTitle;
@@ -59,6 +59,7 @@
MockitoAnnotations.initMocks(this);
mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+
mBroadcastDialog.show();
mDialogView = mBroadcastDialog.mDialogView;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c2fb904..ffd75fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -101,6 +102,9 @@
private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+ @Captor
+ private ArgumentCaptor<AnimatorListenerAdapter> mAnimatorArgumentCaptor;
+
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -478,12 +482,16 @@
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 1)));
mOverlayController.setClipData(mSampleClipData, "");
+ Animator mockFadeoutAnimator = Mockito.mock(Animator.class);
+ when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(true);
verify(mClipboardOverlayView, never()).setMinimized(false);
verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
mCallbacks.onMinimizedViewTapped();
+ verify(mockFadeoutAnimator).addListener(mAnimatorArgumentCaptor.capture());
+ mAnimatorArgumentCaptor.getValue().onAnimationEnd(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(false);
verify(mClipboardOverlayView).showTextPreview("Test Item", false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index eafe727..afd9be5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -107,4 +107,32 @@
assertThat(mSeekbar.getProgress()).isEqualTo(0);
}
+
+ @Test
+ public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
+
+ @Test
+ public void setProgressStateLabels_progressChanged_getExpectedStateDescription() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 6c23254..0a94706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,6 +1,8 @@
package com.android.systemui.dreams
+import android.animation.Animator
import android.animation.AnimatorSet
+import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
@@ -10,13 +12,16 @@
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
@@ -71,6 +76,19 @@
}
@Test
+ fun testExitAnimationUpdatesState() {
+ controller.startExitAnimations(animatorBuilder = { mockAnimator })
+
+ verify(stateController).setExitAnimationsRunning(true)
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+
+ captor.value.onAnimationEnd(mockAnimator)
+ verify(stateController).setExitAnimationsRunning(false)
+ }
+
+ @Test
fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
@@ -87,7 +105,7 @@
fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
- controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
+ controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
@@ -100,4 +118,50 @@
// animator.
verify(mockStartAnimator, times(1)).cancel()
}
+
+ @Test
+ fun testEntryAnimations_translatesUpwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ false,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
+
+ @Test
+ fun testEntryAnimations_translatesDownwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ true,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
}
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 6b095ff..2a72e7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +34,7 @@
import androidx.test.filters.SmallTest;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -65,6 +67,9 @@
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
+ LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+
+ @Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
@@ -109,6 +114,7 @@
mComplicationHostViewController,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
+ mLowLightTransitionCoordinator,
mBlurUtils,
mHandler,
mResources,
@@ -200,7 +206,7 @@
mController.onViewAttached();
- verify(mAnimationsController).startEntryAnimations();
+ verify(mAnimationsController).startEntryAnimations(false);
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -210,11 +216,11 @@
mController.onViewAttached();
- verify(mAnimationsController, never()).startEntryAnimations();
+ verify(mAnimationsController, never()).startEntryAnimations(anyBoolean());
}
@Test
- public void testSkipEntryAnimationsWhenExitingLowLight() {
+ public void testDownwardEntryAnimationsWhenExitingLowLight() {
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
when(mStateController.isLowLightActive()).thenReturn(false);
@@ -230,8 +236,14 @@
mController.onViewAttached();
// Entry animations should be started then immediately ended to skip to the end.
- verify(mAnimationsController).startEntryAnimations();
- verify(mAnimationsController).endAnimations();
+ verify(mAnimationsController).startEntryAnimations(true);
+ }
+
+ @Test
+ public void testStartsExitAnimationsBeforeEnteringLowLight() {
+ mController.onBeforeEnterLowLight();
+
+ verify(mAnimationsController).startExitAnimations();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index dcd8736..068852d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -33,6 +35,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -96,6 +100,10 @@
private ComplicationHostViewController mController;
+ private SecureSettings mSecureSettings;
+
+ private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -108,12 +116,17 @@
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID);
+
mController = new ComplicationHostViewController(
mComplicationHostView,
mLayoutEngine,
mDreamOverlayStateController,
mLifecycleOwner,
- mViewModel);
+ mViewModel,
+ mSecureSettings);
mController.init();
}
@@ -188,6 +201,23 @@
verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
}
+ @Test
+ public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() {
+ //Disable animations
+ mController.mIsAnimationEnabled = false;
+
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
mObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 7f57077..e0ca90e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -18,7 +18,6 @@
import android.app.PendingIntent
import android.content.res.ColorStateList
-import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
@@ -685,46 +684,6 @@
}
@Test
- fun testOnConfigChanged_playersAreAddedBack() {
- mediaCarouselController.pageIndicator = pageIndicator
-
- listener.value.onMediaDataLoaded(
- "playing local",
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- listener.value.onMediaDataLoaded(
- "paused local",
- null,
- DATA.copy(
- active = true,
- isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- runAllReady()
-
- val playersSize = MediaPlayerData.players().size
-
- configListener.value.onConfigChanged(Configuration())
- runAllReady()
-
- verify(pageIndicator).tintList =
- ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
- assertEquals(playersSize, MediaPlayerData.players().size)
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex("playing local"),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
- )
- }
-
- @Test
fun testOnUiModeChanged_playersAreAddedBack() {
mediaCarouselController.pageIndicator = pageIndicator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index af91cdb..0fac3db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.media.controls.ui
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -58,6 +61,8 @@
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var expandedLayout: ConstraintSet
+ @Mock private lateinit var collapsedLayout: ConstraintSet
val delta = 0.1F
@@ -77,6 +82,19 @@
}
@Test
+ fun testOrientationChanged_layoutsAreLoaded() {
+ mediaViewController.expandedLayout = expandedLayout
+ mediaViewController.collapsedLayout = collapsedLayout
+
+ val newConfig = Configuration()
+ newConfig.orientation = ORIENTATION_LANDSCAPE
+ configurationController.onConfigurationChanged(newConfig)
+
+ verify(expandedLayout).load(context, R.xml.media_session_expanded)
+ verify(collapsedLayout).load(context, R.xml.media_session_collapsed)
+ }
+
+ @Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
player.measureState =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 376b7cc..4efc30f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -20,12 +20,17 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -57,8 +62,8 @@
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ private val userTracker: UserTracker = FakeUserTracker()
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@@ -81,16 +86,14 @@
private fun createNoteTaskController(
isEnabled: Boolean = true,
bubbles: Bubbles? = this.bubbles,
- keyguardManager: KeyguardManager? = this.keyguardManager,
- userManager: UserManager? = this.userManager,
): NoteTaskController =
NoteTaskController(
context = context,
resolver = resolver,
eventLogger = eventLogger,
optionalBubbles = Optional.ofNullable(bubbles),
- optionalUserManager = Optional.ofNullable(userManager),
- optionalKeyguardManager = Optional.ofNullable(keyguardManager),
+ userManager = userManager,
+ keyguardManager = keyguardManager,
isEnabled = isEnabled,
devicePolicyManager = devicePolicyManager,
userTracker = userTracker,
@@ -225,13 +228,19 @@
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -259,7 +268,7 @@
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(eventLogger)
@@ -283,13 +292,22 @@
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+
+ (intentCaptor.value.flags and FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK
+
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -306,28 +324,6 @@
}
@Test
- fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
- createNoteTaskController(keyguardManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
- fun showNoteTask_userManagerIsNull_shouldDoNothing() {
- createNoteTaskController(userManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null)
@@ -460,7 +456,7 @@
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
@@ -487,7 +483,7 @@
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index 7f64f8a..0c945df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -22,6 +22,8 @@
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -44,13 +46,14 @@
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
+ private val userTracker: UserTracker = FakeUserTracker()
private lateinit var underTest: NoteTaskInfoResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
+ underTest = NoteTaskInfoResolver(roleManager, packageManager, userTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index d6dfc85..ac106ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -57,6 +57,7 @@
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -351,4 +352,44 @@
.startPendingIntentDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
+
+ @Test
+ fun testActiveTileListensOnceAfterCreated() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager).setBindRequested(true)
+ verify(tileService).onStartListening()
+ }
+
+ @Test
+ fun testActiveTileDoesntListenAfterFirstTime() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ // Make sure we have an icon in the tile because we don't have a default icon
+ // This should not be overridden by the retrieved tile that has null icon.
+ tile.qsTile.icon = mock(Icon::class.java)
+ `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java)))
+ .thenReturn(mock(Drawable::class.java))
+
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ // postStale will set it to not listening after it's done
+ verify(tileService).onStopListening()
+
+ clearInvocations(tileServiceManager, tileService)
+
+ tile.setListening(Any(), true)
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager, never()).setBindRequested(true)
+ verify(tileService, never()).onStartListening()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
new file mode 100644
index 0000000..eac0e29
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.Notification
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.SystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class GroupWhenCoordinatorTest : SysuiTestCase() {
+
+ private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+ private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
+
+ @Mock private lateinit var pipeline: NotifPipeline
+
+ @Mock private lateinit var delayableExecutor: DelayableExecutor
+
+ @Mock private lateinit var groupController: NotifGroupController
+
+ @Mock private lateinit var systemClock: SystemClock
+
+ @InjectMocks private lateinit var coordinator: GroupWhenCoordinator
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ whenever(systemClock.currentTimeMillis()).thenReturn(NOW)
+ coordinator.attach(pipeline)
+
+ beforeFinalizeFilterListener = withArgCaptor {
+ verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
+ afterRenderGroupListener = withArgCaptor {
+ verify(pipeline).addOnAfterRenderGroupListener(capture())
+ }
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW - 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW - 100L)
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW - 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 10L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+ val childEntry4 = buildNotificationEntry(4, NOW - 9L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_filterInvalidNotificationTimes() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, -20000L)
+ val childEntry3 = buildNotificationEntry(4, 0)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 100))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, 0)
+ val childEntry2 = buildNotificationEntry(2, -1)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController, never()).setNotificationGroupWhen(NOW)
+ }
+
+ @Test
+ fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1000L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 2000L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(delayableExecutor).executeDelayed(any(), eq(1000))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1L)
+ val prevInvalidation = mock<Runnable>()
+ whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation)
+
+ val groupEntry =
+ GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ // THEN
+ verify(prevInvalidation).run()
+ }
+
+ private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry {
+ val notification = Notification.Builder(mContext).setWhen(timeMillis).build()
+ val sbn = SbnBuilder().setNotification(notification).build()
+ return NotificationEntryBuilder().setId(id).setSbn(sbn).build()
+ }
+
+ private companion object {
+ private const val NOW = 1000L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index dd7143a..cbf841b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack;
import static android.view.View.GONE;
+import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
@@ -46,6 +47,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -54,6 +56,8 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
@@ -91,6 +95,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
@@ -843,6 +849,19 @@
verify(mEmptyShadeView).setFooterText(not(0));
}
+ @Test
+ public void testWindowInsetAnimationProgress_updatesBottomInset() {
+ int bottomImeInset = 100;
+ mStackScrollerInternal.setAnimatedInsetsEnabled(true);
+ WindowInsets windowInsets = new WindowInsets.Builder()
+ .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
+ ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
+ mStackScrollerInternal
+ .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations);
+
+ assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 1779de7..7594c90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,13 +8,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -26,6 +27,9 @@
@Mock
private lateinit var display: Display
+ @Mock
+ private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+
private val view: View = View(context)
private val progressProvider = TestUnfoldTransitionProvider()
private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
@@ -36,9 +40,9 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(windowManager.defaultDisplay).thenReturn(display)
- `when`(display.rotation).thenReturn(Surface.ROTATION_0)
- `when`(display.getSize(any())).thenAnswer {
+ whenever(windowManager.defaultDisplay).thenReturn(display)
+ whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+ whenever(display.getSize(any())).thenAnswer {
val point = it.arguments[0] as Point
point.x = 100
point.y = 100
@@ -47,7 +51,12 @@
scopedProvider.setReadyToHandleTransition(true)
- controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager)
+ controller =
+ StatusBarMoveFromCenterAnimationController(
+ scopedProvider,
+ currentActivityTypeProvider,
+ windowManager
+ )
}
@Test
@@ -99,6 +108,31 @@
}
@Test
+ fun alpha_onLauncher_alphaDoesNotChange() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(true)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.0f)
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(1.0f)
+
+ assertThat(view.alpha).isEqualTo(1.0f)
+ }
+
+ @Test
+ fun alpha_NotOnLauncher_alphaChanges() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(false)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ assertThat(view.alpha).isNotEqualTo(1.0f)
+ }
+
+ @Test
fun transitionFinished_viewReAttached_noChangesToTranslation() {
controller.onViewsReady(arrayOf(view))
progressProvider.onTransitionProgress(0.5f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 86529dc..35dea60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
index 0145103..dfef62e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -24,8 +24,8 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 17502f2..07c8cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -27,13 +27,13 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index b2577e3..bd5a4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -53,6 +53,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -65,7 +66,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 09b7a66..68b1cda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,12 +37,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
new file mode 100644
index 0000000..4aa48d6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui
+
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.KeyguardMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileViewLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+ private val stringWriter = StringWriter()
+ private val printWriter = PrintWriter(stringWriter)
+
+ private val underTest = MobileViewLogger(buffer, mock())
+
+ @Mock private lateinit var flags: StatusBarPipelineFlags
+ @Mock private lateinit var commonViewModel: MobileIconViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun collectionStarted_dumpHasInfo() {
+ val view = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStarted_multipleViews_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStopped_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+ underTest.logCollectionStopped(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=false")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ private fun getDumpString(): String {
+ underTest.dump(printWriter, args = arrayOf())
+ return stringWriter.toString()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index e68a397..7420db2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
@@ -60,6 +61,7 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var viewLogger: MobileViewLogger
@Mock private lateinit var constants: ConnectivityConstants
private lateinit var interactor: FakeMobileIconInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -94,7 +96,13 @@
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
@@ -109,8 +117,13 @@
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
ViewUtils.attachView(view)
@@ -124,8 +137,13 @@
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
ViewUtils.attachView(view)
@@ -142,8 +160,13 @@
whenever(constants.hasDataCapabilities).thenReturn(false)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -157,8 +180,13 @@
whenever(constants.hasDataCapabilities).thenReturn(true)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -171,8 +199,13 @@
fun isIconVisible_notAirplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -185,8 +218,13 @@
fun isIconVisible_airplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(true)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -198,7 +236,13 @@
@Test
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -214,7 +258,13 @@
@Test
fun setStaticDrawableColor_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index f983030..a6d9152 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -84,7 +85,7 @@
testScope.backgroundScope,
)
- homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 4628f84..ddb7f4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -24,6 +24,8 @@
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -51,6 +53,8 @@
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var logger: MobileViewLogger
+ @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -73,6 +77,8 @@
underTest =
MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index e4c8fd0..b4039d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -164,6 +164,10 @@
override fun getShouldIconBeVisible(): Boolean {
return shouldIconBeVisibleInternal
}
+
+ override fun isCollecting(): Boolean {
+ return true
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 48b1732..481d453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -38,6 +38,7 @@
import com.android.internal.view.RotationPolicy;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
@@ -55,10 +56,12 @@
private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
+ @Mock private DeviceStateManager mDeviceStateManager;
+ @Mock private DeviceStateRotationLockSettingControllerLogger mLogger;
+ @Mock private DumpManager mDumpManager;
+
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
- @Mock
- private DeviceStateManager mDeviceStateManager;
private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
@@ -78,7 +81,13 @@
mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
mDeviceStateRotationLockSettingController =
new DeviceStateRotationLockSettingController(
- mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager);
+ mFakeRotationPolicy,
+ mDeviceStateManager,
+ mFakeExecutor,
+ mSettingsManager,
+ mLogger,
+ mDumpManager
+ );
mDeviceStateRotationLockSettingController.setListening(true);
verify(mDeviceStateManager)
@@ -173,15 +182,11 @@
}
@Test
- public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
- initializeSettingsWith(
- 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
- mFakeRotationPolicy.setRotationLock(true);
-
- mDeviceStateCallback.onStateChanged(1);
- assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
-
+ public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() {
mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+
+ mDeviceStateCallback.onStateChanged(2);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index a079668..c3a6cf0 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -27,6 +27,7 @@
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -67,6 +68,7 @@
}
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+ val hingeAngleProvider: HingeAngleProvider
val rotationChangeProvider: RotationChangeProvider
}
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index a94ab34..fa30a6f 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -255,7 +255,7 @@
broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW);
mFlashBroadcastReceiver = new FlashBroadcastReceiver();
mContext.registerReceiver(
- mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_EXPORTED);
+ mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_NOT_EXPORTED);
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index aaa376a..bffa3dd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1672,57 +1672,62 @@
return;
}
- synchronized (mRecords) {
- String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
- + " state=" + state;
- if (VDBG) {
- log(str);
- }
- mLocalLog.log(str);
- // for service state updates, don't notify clients when subId is invalid. This prevents
- // us from sending incorrect notifications like b/133140128
- // In the future, we can remove this logic for every notification here and add a
- // callback so listeners know when their PhoneStateListener's subId becomes invalid, but
- // for now we use the simplest fix.
- if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
- mServiceState[phoneId] = state;
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mRecords) {
+ String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId="
+ + phoneId + " state=" + state;
+ if (VDBG) {
+ log(str);
+ }
+ mLocalLog.log(str);
+ // for service state updates, don't notify clients when subId is invalid. This
+ // prevents us from sending incorrect notifications like b/133140128
+ // In the future, we can remove this logic for every notification here and add a
+ // callback so listeners know when their PhoneStateListener's subId becomes invalid,
+ // but for now we use the simplest fix.
+ if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
+ mServiceState[phoneId] = state;
- for (Record r : mRecords) {
- if (VDBG) {
- log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
- + " phoneId=" + phoneId + " state=" + state);
- }
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
+ for (Record r : mRecords) {
+ if (VDBG) {
+ log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
+ + " phoneId=" + phoneId + " state=" + state);
+ }
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
- try {
- ServiceState stateToSend;
- if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- stateToSend = new ServiceState(state);
- } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
- stateToSend = state.createLocationInfoSanitizedCopy(false);
- } else {
- stateToSend = state.createLocationInfoSanitizedCopy(true);
+ try {
+ ServiceState stateToSend;
+ if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+ stateToSend = new ServiceState(state);
+ } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
+ stateToSend = state.createLocationInfoSanitizedCopy(false);
+ } else {
+ stateToSend = state.createLocationInfoSanitizedCopy(true);
+ }
+ if (DBG) {
+ log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
+ + " subId=" + subId + " phoneId=" + phoneId
+ + " state=" + stateToSend);
+ }
+ r.callback.onServiceStateChanged(stateToSend);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
}
- if (DBG) {
- log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
- + " subId=" + subId + " phoneId=" + phoneId
- + " state=" + stateToSend);
- }
- r.callback.onServiceStateChanged(stateToSend);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
}
}
+ } else {
+ log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
+ + " or subId=" + subId);
}
- } else {
- log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
- + " or subId=" + subId);
+ handleRemoveListLocked();
}
- handleRemoveListLocked();
+ broadcastServiceStateChanged(state, phoneId, subId);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
- broadcastServiceStateChanged(state, phoneId, subId);
}
public void notifySimActivationStateChangedForPhoneId(int phoneId, int subId,
@@ -3508,13 +3513,10 @@
public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
- final long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.notePhoneState(state.getState());
} catch (RemoteException re) {
// Can't do much
- } finally {
- Binder.restoreCallingIdentity(ident);
}
// Send the broadcast exactly once to all possible disjoint sets of apps.
@@ -3531,8 +3533,7 @@
// - Sanitized ServiceState sent to all other apps with READ_PHONE_STATE
// - Sanitized ServiceState sent to all other apps with READ_PRIVILEGED_PHONE_STATE but not
// READ_PHONE_STATE
- if (Binder.withCleanCallingIdentity(() ->
- LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId()))) {
+ if (LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId())) {
Intent fullIntent = createServiceStateIntent(state, subId, phoneId, false);
mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
fullIntent,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0ee883f..f236a96 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,6 +402,7 @@
public void systemServicesReady() {
mStats.systemServicesReady(mContext);
+ mCpuWakeupStats.systemServicesReady();
mWorker.systemServicesReady();
final INetworkManagementService nms = INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index bb8d3f4..23a384f 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -67,8 +67,8 @@
private SparseIntArray mPendingUidStates = new SparseIntArray();
private SparseIntArray mCapability = new SparseIntArray();
private SparseIntArray mPendingCapability = new SparseIntArray();
- private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray();
- private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray();
+ private SparseBooleanArray mAppWidgetVisible = new SparseBooleanArray();
+ private SparseBooleanArray mPendingAppWidgetVisible = new SparseBooleanArray();
private SparseLongArray mPendingCommitTime = new SparseLongArray();
private SparseBooleanArray mPendingGone = new SparseBooleanArray();
@@ -140,7 +140,7 @@
private int evalModeInternal(int uid, int code, int uidState, int uidCapability) {
- if (getUidVisibleAppWidget(uid) || mActivityManagerInternal.isPendingTopUid(uid)
+ if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid)
|| mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) {
return MODE_ALLOWED;
}
@@ -205,7 +205,7 @@
int numUids = uidPackageNames.size();
for (int i = 0; i < numUids; i++) {
int uid = uidPackageNames.keyAt(i);
- mPendingVisibleAppWidget.put(uid, visible);
+ mPendingAppWidgetVisible.put(uid, visible);
commitUidPendingState(uid);
}
@@ -291,9 +291,9 @@
ActivityManager.printCapabilitiesFull(pw, pendingCapability);
pw.println();
}
- boolean appWidgetVisible = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
// if no pendingAppWidgetVisible set to appWidgetVisible to suppress output
- boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible);
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible);
pw.print(" appWidgetVisible=");
pw.println(appWidgetVisible);
if (appWidgetVisible != pendingAppWidgetVisible) {
@@ -333,25 +333,25 @@
mUidStates.get(uid, MIN_PRIORITY_UID_STATE));
int pendingCapability = mPendingCapability.get(uid,
mCapability.get(uid, PROCESS_CAPABILITY_NONE));
- boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid,
- mVisibleAppWidget.get(uid, false));
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid,
+ mAppWidgetVisible.get(uid, false));
int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
- boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
if (uidState != pendingUidState
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget) {
+ || appWidgetVisible != pendingAppWidgetVisible) {
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
!= pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget;
+ || appWidgetVisible != pendingAppWidgetVisible;
if (foregroundChange) {
// To save on memory usage, log only interesting changes.
mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability,
- pendingVisibleAppWidget);
+ pendingAppWidgetVisible, appWidgetVisible != pendingAppWidgetVisible);
}
for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
@@ -367,17 +367,17 @@
if (mPendingGone.get(uid, false)) {
mUidStates.delete(uid);
mCapability.delete(uid);
- mVisibleAppWidget.delete(uid);
+ mAppWidgetVisible.delete(uid);
mPendingGone.delete(uid);
} else {
mUidStates.put(uid, pendingUidState);
mCapability.put(uid, pendingCapability);
- mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ mAppWidgetVisible.put(uid, pendingAppWidgetVisible);
}
mPendingUidStates.delete(uid);
mPendingCapability.delete(uid);
- mPendingVisibleAppWidget.delete(uid);
+ mPendingAppWidgetVisible.delete(uid);
mPendingCommitTime.delete(uid);
}
@@ -385,8 +385,8 @@
return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
}
- private boolean getUidVisibleAppWidget(int uid) {
- return mVisibleAppWidget.get(uid, false);
+ private boolean getUidAppWidgetVisible(int uid) {
+ return mAppWidgetVisible.get(uid, false);
}
private static class EventLog {
@@ -398,6 +398,9 @@
// Memory usage: 24 * size bytes
private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
+ private static final int APP_WIDGET_VISIBLE = 1 << 0;
+ private static final int APP_WIDGET_VISIBLE_CHANGED = 1 << 1;
+
private final DelayableExecutor mExecutor;
private final Thread mExecutorThread;
@@ -446,16 +449,18 @@
mUpdateUidProcStateLogTimestamps[idx] = timestamp;
}
- void logCommitUidState(int uid, int uidState, int capability, boolean visible) {
+ void logCommitUidState(int uid, int uidState, int capability, boolean appWidgetVisible,
+ boolean appWidgetVisibleChanged) {
if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
return;
}
mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
- this, System.currentTimeMillis(), uid, uidState, capability, visible));
+ this, System.currentTimeMillis(), uid, uidState, capability, appWidgetVisible,
+ appWidgetVisibleChanged));
}
void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability,
- boolean visible) {
+ boolean appWidgetVisible, boolean appWidgetVisibleChanged) {
int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize)
% COMMIT_UID_STATE_LOG_MAX_SIZE;
if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) {
@@ -468,7 +473,13 @@
mCommitUidStateLog[idx][0] = uid;
mCommitUidStateLog[idx][1] = uidState;
mCommitUidStateLog[idx][2] = capability;
- mCommitUidStateLog[idx][3] = visible ? 1 : 0;
+ mCommitUidStateLog[idx][3] = 0;
+ if (appWidgetVisible) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE;
+ }
+ if (appWidgetVisibleChanged) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE_CHANGED;
+ }
mCommitUidStateLogTimestamps[idx] = timestamp;
}
@@ -570,7 +581,9 @@
int uid = mCommitUidStateLog[idx][0];
int uidState = mCommitUidStateLog[idx][1];
int capability = mCommitUidStateLog[idx][2];
- boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0;
+ boolean appWidgetVisible = (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE) != 0;
+ boolean appWidgetVisibleChanged =
+ (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE_CHANGED) != 0;
TimeUtils.dumpTime(pw, timestamp);
@@ -585,8 +598,12 @@
pw.print(" capability=");
pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
- pw.print(" visibleAppWidget=");
- pw.print(visibleAppWidget);
+ pw.print(" appWidgetVisible=");
+ pw.print(appWidgetVisible);
+
+ if (appWidgetVisibleChanged) {
+ pw.print(" (changed)");
+ }
pw.println();
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d0e576..c50e275 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8588,7 +8588,9 @@
if (isMutable()) {
// For call stream, align mute only when muted, not when index is set to 0
mVolumeGroupState.mute(
- forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
+ forceMuteState ? mIsMuted :
+ (groupIndex == 0 && !isCallStream(mStreamType))
+ || mIsMuted);
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55d2921..ea157c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -128,6 +128,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.Spline;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
@@ -250,6 +251,7 @@
private ActivityManagerInternal mActivityManagerInternal;
private ActivityManager mActivityManager;
private UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ @Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@GuardedBy("mSyncRoot")
@@ -1494,8 +1496,9 @@
final long token = Binder.clearCallingIdentity();
try {
+ final int displayId;
synchronized (mSyncRoot) {
- final int displayId =
+ displayId =
createVirtualDisplayLocked(
callback,
projection,
@@ -1509,8 +1512,39 @@
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
}
- return displayId;
}
+
+ // When calling setContentRecordingSession into the WindowManagerService, the WMS
+ // attempts to acquire a lock before executing its main body. Due to this, we need
+ // to be sure that it isn't called while the DisplayManagerService is also holding
+ // a lock, to avoid a deadlock scenario.
+ final ContentRecordingSession session =
+ virtualDisplayConfig.getContentRecordingSession();
+
+ if (displayId != Display.INVALID_DISPLAY && session != null) {
+ // Only attempt to set content recording session if there are details to set and a
+ // VirtualDisplay has been successfully constructed.
+ session.setDisplayId(displayId);
+
+ // We set the content recording session here on the server side instead of using
+ // a second AIDL call in MediaProjection. By ensuring that a virtual display has
+ // been constructed before calling setContentRecordingSession, we avoid a race
+ // condition between the DMS & WMS which could lead to the MediaProjection
+ // being pre-emptively torn down.
+ if (!mWindowManagerInternal.setContentRecordingSession(session)) {
+ // Unable to start mirroring, so tear down projection & release VirtualDisplay.
+ try {
+ getProjectionService().stopActiveProjection();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the "
+ + "active projection", e);
+ }
+ releaseVirtualDisplayInternal(callback.asBinder());
+ return Display.INVALID_DISPLAY;
+ }
+ }
+
+ return displayId;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2804,8 +2838,7 @@
private IMediaProjectionManager getProjectionService() {
if (mProjectionService == null) {
- IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
- mProjectionService = IMediaProjectionManager.Stub.asInterface(b);
+ mProjectionService = mInjector.getProjectionService();
}
return mProjectionService;
}
@@ -2964,6 +2997,11 @@
boolean getHdrOutputConversionSupport() {
return DisplayControl.getHdrOutputConversionSupport();
}
+
+ IMediaProjectionManager getProjectionService() {
+ IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
+ return IMediaProjectionManager.Stub.asInterface(b);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index eda15ae..4f7a2ba 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -88,7 +88,17 @@
// Called with SyncRoot lock held.
public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
- this(syncRoot, context, handler, listener, DisplayControl::createDisplay);
+ this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
+ return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ DisplayControl.destroyDisplay(displayToken);
+ }
+ });
}
@VisibleForTesting
@@ -311,7 +321,7 @@
mSurface.release();
mSurface = null;
}
- DisplayControl.destroyDisplay(getDisplayTokenLocked());
+ mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked());
if (mProjection != null && mMediaProjectionCallback != null) {
try {
mProjection.unregisterCallback(mMediaProjectionCallback);
@@ -653,5 +663,12 @@
* @return The token reference for the display in SurfaceFlinger.
*/
IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
+
+ /**
+ * Destroy a display in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display to be destroyed.
+ */
+ void destroyDisplay(IBinder displayToken);
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index ab84ae4..df70a32 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -39,26 +39,24 @@
@Override
public int onCommand(String cmd) {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.ROOT_UID) {
- Slog.e(TAG, "Must be root before calling Dream shell commands");
- return -1;
- }
-
- if (TextUtils.isEmpty(cmd)) {
- return super.handleDefaultCommands(cmd);
- }
if (DEBUG) {
Slog.d(TAG, "onCommand:" + cmd);
}
- switch (cmd) {
- case "start-dreaming":
- return startDreaming();
- case "stop-dreaming":
- return stopDreaming();
- default:
- return super.handleDefaultCommands(cmd);
+ try {
+ switch (cmd) {
+ case "start-dreaming":
+ enforceCallerIsRoot();
+ return startDreaming();
+ case "stop-dreaming":
+ enforceCallerIsRoot();
+ return stopDreaming();
+ default:
+ return super.handleDefaultCommands(cmd);
+ }
+ } catch (SecurityException e) {
+ getOutPrintWriter().println(e);
+ return -1;
}
}
@@ -72,6 +70,12 @@
return 0;
}
+ private void enforceCallerIsRoot() {
+ if (Binder.getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Must be root to call Dream shell commands");
+ }
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 2174f40..c962bc4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -295,6 +295,7 @@
}
ipw.println("Capabilities: " + mGnssNative.getCapabilities());
+ ipw.println("GNSS Hardware Model Name: " + getGnssHardwareModelName());
if (mGnssStatusProvider.isSupported()) {
ipw.println("Status Provider:");
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
new file mode 100644
index 0000000..b9c9bae
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+/* package */ final class AudioAttributesUtils {
+
+ /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+
+ private AudioAttributesUtils() {
+ // no-op to prevent instantiation.
+ }
+
+ @MediaRoute2Info.Type
+ /* package */ static int mapToMediaRouteType(
+ @NonNull AudioDeviceAttributes audioDeviceAttributes) {
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ return MediaRoute2Info.TYPE_WIRED_HEADSET;
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ return MediaRoute2Info.TYPE_DOCK;
+ case AudioDeviceInfo.TYPE_HDMI:
+ return MediaRoute2Info.TYPE_HDMI;
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return MediaRoute2Info.TYPE_USB_DEVICE;
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ default:
+ return MediaRoute2Info.TYPE_UNKNOWN;
+ }
+ }
+
+
+ /* package */ static boolean isDeviceOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ case AudioDeviceInfo.TYPE_HDMI:
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /* package */ static boolean isBluetoothOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ case AudioDeviceInfo.TYPE_BLE_SPEAKER:
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index d4a1184..66985e0 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -53,7 +53,16 @@
return new NoOpBluetoothRouteController();
}
- return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ } else {
+ return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index d7893ee..3875c84 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -44,10 +44,22 @@
IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- return new LegacyDeviceRouteController(context,
- audioManager,
- audioService,
- onDeviceRouteChangedListener);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ } else {
+ return new AudioPoliciesDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
index 723cda0..70ee38f 100644
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
@@ -32,7 +32,7 @@
private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
@StringDef(prefix = "FEATURE_", value = {
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER
})
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.SOURCE)
@@ -43,7 +43,7 @@
* 'Audio Strategies'-aware controller.
*/
/* package */ static final @MediaFeatureFlag String
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER =
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER =
"BluetoothRouteController__enable_legacy_bluetooth_routes_controller";
private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
@@ -52,7 +52,7 @@
// Empty to prevent instantiation.
}
- /* package */ MediaFeatureFlagManager getInstance() {
+ /* package */ static MediaFeatureFlagManager getInstance() {
return sInstance;
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6619e6c..5d5c621 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,11 +16,14 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
@@ -37,6 +40,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.List;
import java.util.Objects;
/**
@@ -71,6 +75,26 @@
private final AudioManagerBroadcastReceiver mAudioReceiver =
new AudioManagerBroadcastReceiver();
+ private final AudioManager.OnDevicesForAttributesChangedListener
+ mOnDevicesForAttributesChangedListener =
+ new AudioManager.OnDevicesForAttributesChangedListener() {
+ @Override
+ public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
+ return;
+ }
+
+ mHandler.post(() -> {
+ updateSelectedAudioDevice(devices);
+ notifyProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ }
+ };
+
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -100,8 +124,15 @@
});
});
+ mAudioManager.addOnDevicesForAttributesChangedListener(
+ AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
+ mOnDevicesForAttributesChangedListener);
+
// These methods below should be called after all fields are initialized, as they
// access the fields inside.
+ List<AudioDeviceAttributes> devices =
+ mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
+ updateSelectedAudioDevice(devices);
updateProviderState();
updateSessionInfosIfNeeded();
}
@@ -239,6 +270,26 @@
}
}
+ private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ Slog.w(TAG, "The list of preferred devices was empty.");
+ return;
+ }
+
+ AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
+
+ if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(
+ AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
+ mBluetoothRouteController.selectRoute(null);
+ } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(null);
+ mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
+ } else {
+ Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
+ }
+ }
+
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9e01c7a..84bee50 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -39,6 +39,7 @@
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -85,6 +86,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -107,6 +109,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@@ -623,7 +626,7 @@
// package does not exist; should not happen
return null;
}
- return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo);
+ return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -676,7 +679,7 @@
continue;
}
results.add(new LauncherActivityInfoInternal(ri.activityInfo,
- incrementalStatesInfo));
+ incrementalStatesInfo, user));
}
return results;
}
@@ -1078,6 +1081,55 @@
}
@Override
+ @NonNull
+ public Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage,
+ int userId) {
+ ensureShortcutPermission(callingPackage);
+ int callingUid = Binder.getCallingUid();
+ final long callerIdentity = Binder.clearCallingIdentity();
+ try {
+ Map<String, LauncherActivityInfoInternal> shortcutOverridesInfo = new ArrayMap<>();
+ UserHandle managedUserHandle = getManagedProfile(userId);
+ if (managedUserHandle == null) {
+ return shortcutOverridesInfo;
+ }
+
+ List<String> packagesToOverride =
+ DevicePolicyCache.getInstance().getLauncherShortcutOverrides();
+ for (String packageName : packagesToOverride) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+
+ List<LauncherActivityInfoInternal> possibleShortcutOverrides =
+ queryIntentLauncherActivities(
+ intent,
+ callingUid,
+ managedUserHandle
+ );
+
+ if (!possibleShortcutOverrides.isEmpty()) {
+ shortcutOverridesInfo.put(packageName, possibleShortcutOverrides.get(0));
+ }
+ }
+ return shortcutOverridesInfo;
+ } finally {
+ Binder.restoreCallingIdentity(callerIdentity);
+ }
+ }
+
+
+ @Nullable
+ private UserHandle getManagedProfile(int userId) {
+ for (UserInfo profile : mUm.getProfiles(userId)) {
+ if (profile.isManagedProfile()) {
+ return profile.getUserHandle();
+ }
+ }
+ return null;
+ }
+
+ @Override
public boolean startShortcut(String callingPackage, String packageName, String featureId,
String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
int targetUserId) {
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 706aedc..b05b662 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -22,7 +22,9 @@
import android.content.Context;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.LongSparseArray;
@@ -40,6 +42,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,13 +56,13 @@
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
private static final String SUBSYSTEM_ALARM_WIFI = "Wifi";
@VisibleForTesting
- static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
- @VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
- private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes.
+ private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
+ @VisibleForTesting
+ final Config mConfig = new Config();
private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
@VisibleForTesting
@@ -72,6 +76,14 @@
mHandler = handler;
}
+ /**
+ * Called on the boot phase SYSTEM_SERVICES_READY.
+ * This ensures that DeviceConfig is ready for calls to read properties.
+ */
+ public synchronized void systemServicesReady() {
+ mConfig.register(new HandlerExecutor(mHandler));
+ }
+
private static int subsystemToStatsReason(int subsystem) {
switch (subsystem) {
case CPU_WAKEUP_SUBSYSTEM_ALARM:
@@ -136,14 +148,15 @@
// we can delete all history that will not be useful in attributing future wakeups.
mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
- // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that
+ // Limit history of wakeups and their attribution to the last retentionDuration. Note that
// the last wakeup and its attribution (if computed) is always stored, even if that wakeup
- // had occurred before WAKEUP_RETENTION_MS.
- int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ // had occurred before retentionDuration.
+ final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
+ int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupEvents.removeAt(i);
}
- lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupAttribution.removeAt(i);
}
@@ -226,6 +239,9 @@
pw.println("CPU wakeup stats:");
pw.increaseIndent();
+ mConfig.dump(pw);
+ pw.println();
+
mIrqDeviceMap.dump(pw);
pw.println();
@@ -296,7 +312,8 @@
}
private static final class WakingActivityHistory {
- private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours.
+ private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
+
private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
new SparseArray<>();
@@ -521,4 +538,52 @@
}
}
}
+
+ static final class Config implements DeviceConfig.OnPropertiesChangedListener {
+ static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms";
+
+ private static final String[] PROPERTY_NAMES = {
+ KEY_WAKEUP_STATS_RETENTION_MS,
+ };
+
+ static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3);
+
+ /**
+ * Wakeup stats are retained only for this duration.
+ */
+ public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS;
+
+ void register(Executor executor) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ executor, this);
+ onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ PROPERTY_NAMES));
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ continue;
+ }
+ switch (name) {
+ case KEY_WAKEUP_STATS_RETENTION_MS:
+ WAKEUP_STATS_RETENTION_MS = properties.getLong(
+ KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS);
+ break;
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Config:");
+
+ pw.increaseIndent();
+
+ pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS);
+ pw.println();
+
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 5bace0e..88d64df 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -151,6 +151,13 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
static final long REQUEST_LISTENING_MUST_MATCH_PACKAGE = 172251878L;
+ /**
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long REQUEST_LISTENING_OTHER_USER_NOOP = 242194868L;
+
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -1888,7 +1895,12 @@
// Check current user
if (userId != currentUser) {
- throw new IllegalArgumentException("User " + userId + " is not the current user.");
+ if (CompatChanges.isChangeEnabled(REQUEST_LISTENING_OTHER_USER_NOOP, callingUid)) {
+ return;
+ } else {
+ throw new IllegalArgumentException(
+ "User " + userId + " is not the current user.");
+ }
}
}
if (mBar != null) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 404ca01..c6684df 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2125,6 +2125,26 @@
}
@Override
+ public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftEnablePositionTracking");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .notifyTvMessage(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startRecording(IBinder sessionToken, @Nullable Uri programUri,
@Nullable Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 37e4890..41fee1c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8145,7 +8145,13 @@
}
// Clear config override in #updateCompatDisplayInsets().
- onRequestedOverrideConfigurationChanged(EMPTY);
+ final int activityType = getActivityType();
+ final Configuration overrideConfig = getRequestedOverrideConfiguration();
+ overrideConfig.unset();
+ // Keep the activity type which was set when attaching to a task to prevent leaving it
+ // undefined.
+ overrideConfig.windowConfiguration.setActivityType(activityType);
+ onRequestedOverrideConfigurationChanged(overrideConfig);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index df471c5..710c4af 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1729,14 +1729,11 @@
}
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
- Toast toast = Toast.makeText(mService.mContext,
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
(ActivitySecurityModelFeatureFlags.DOC_LINK
- + (restrictActivitySwitch
- ? "returned home due to "
- : "would return home due to ")
- + callingLabel),
- Toast.LENGTH_LONG);
- UiThread.getHandler().post(toast::show);
+ + (restrictActivitySwitch ? " returned home due to "
+ : " would return home due to ")
+ + callingLabel), Toast.LENGTH_LONG).show());
}
// If the activity switch should be restricted, return home rather than the
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 09f7fb6..cf0fc09 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2018,16 +2018,19 @@
/**
* Like isOnScreen(), but we don't return true if the window is part
- * of a transition that has not yet been started.
+ * of a transition but has not yet started animating.
*/
boolean isReadyForDisplay() {
- if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
+ if (!mHasSurface || mDestroying || !isVisibleByPolicy()) {
+ return false;
+ }
+ if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()
+ && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
return false;
}
final boolean parentAndClientVisible = !isParentWindowHidden()
&& mViewVisibility == View.VISIBLE && mToken.isVisible();
- return mHasSurface && isVisibleByPolicy() && !mDestroying
- && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
+ return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL);
}
boolean isFullyTransparent() {
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index c8ec7c2..e84f0cc 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -20,17 +20,18 @@
import android.content.ComponentName;
import android.content.Context;
import android.credentials.ClearCredentialStateRequest;
+import android.credentials.CredentialProviderInfo;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -120,22 +121,22 @@
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onSuccess();
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
Log.i(TAG, "Issue while propagating the response to the client");
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -144,7 +145,7 @@
Log.i(TAG, "respondToClientWithErrorAndFinish");
if (isSessionCancelled()) {
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -154,7 +155,7 @@
e.printStackTrace();
}
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
finishSession(/*propagateCancellation=*/false);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 0c1133c..7e1780d 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -16,9 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -27,17 +24,18 @@
import android.credentials.CreateCredentialRequest;
import android.credentials.CreateCredentialResponse;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -103,11 +101,11 @@
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
}
@@ -144,18 +142,18 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client: " + e.getMessage());
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -168,7 +166,7 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -184,10 +182,10 @@
private void logFailureOrUserCancel(String errorType) {
if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 41ae911..9c87005 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,15 +36,14 @@
import android.credentials.CredentialDescription;
import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
-import android.credentials.ListEnabledProvidersResponse;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
@@ -56,7 +55,7 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -118,10 +117,11 @@
int resolvedUserId) {
List<CredentialManagerServiceImpl> services = new ArrayList<>();
List<CredentialProviderInfo> serviceInfos =
- CredentialProviderInfo.getAvailableSystemServices(
+ CredentialProviderInfoFactory.getAvailableSystemServices(
mContext,
resolvedUserId,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ new HashSet<>());
serviceInfos.forEach(
info -> {
services.add(
@@ -222,10 +222,16 @@
return hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- private void verifyPermission(String permission) throws SecurityException {
- if (!hasPermission(permission)) {
- throw new SecurityException("Caller is missing permission: " + permission);
+ private void verifyGetProvidersPermission() throws SecurityException {
+ if (hasPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)) {
+ return;
}
+
+ if (hasPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)) {
+ return;
+ }
+
+ throw new SecurityException("Caller is missing permission: QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS");
}
private boolean hasPermission(String permission) {
@@ -550,40 +556,6 @@
providerSessions.forEach(ProviderSession::invokeSession);
}
- @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders
- // to be guarded by 'service.mLock', which is the same as mLock.
- @Override
- public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) {
- Log.i(TAG, "listEnabledProviders");
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- if (!hasWriteSecureSettingsPermission()) {
- try {
- callback.onError(
- PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
- } catch (RemoteException e) {
- Log.e(TAG, "Issue with invoking response: " + e.getMessage());
- }
- return cancelTransport;
- }
-
- List<String> enabledProviders = new ArrayList<>();
- runForUser(
- (service) -> {
- enabledProviders.add(service.getComponentName().flattenToString());
- });
-
- // Call the callback.
- try {
- callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders));
- } catch (RemoteException e) {
- Log.i(TAG, "Issue with invoking response: " + e.getMessage());
- // TODO: Propagate failure
- }
-
- return cancelTransport;
- }
-
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
@@ -659,7 +631,7 @@
// The component name and the package name do not match.
MetricUtilities.logApiCalled(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_FAILURE, callingUid);
+ ApiStatus.FAILURE, callingUid);
Log.w(
TAG,
"isEnabledCredentialProviderService: Component name does not"
@@ -667,7 +639,7 @@
return false;
}
MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid);
+ ApiStatus.SUCCESS, callingUid);
return true;
}
}
@@ -677,20 +649,35 @@
}
@Override
- public List<ServiceInfo> getCredentialProviderServices(
- int userId, boolean disableSystemAppVerificationForTests, int providerFilter) {
+ public List<CredentialProviderInfo> getCredentialProviderServices(
+ int userId, int providerFilter) {
Log.i(TAG, "getCredentialProviderServices");
- verifyPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS);
+ verifyGetProvidersPermission();
- List<ServiceInfo> services = new ArrayList<>();
- List<CredentialProviderInfo> providers =
- CredentialProviderInfo.getCredentialProviderServices(
- mContext, userId, disableSystemAppVerificationForTests, providerFilter);
- for (CredentialProviderInfo p : providers) {
- services.add(p.getServiceInfo());
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ @Override
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ int providerFilter) {
+ Log.i(TAG, "getCredentialProviderServicesForTesting");
+ verifyGetProvidersPermission();
+
+ final int userId = UserHandle.getCallingUserId();
+ return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ private Set<ServiceInfo> getEnabledProviders() {
+ Set<ServiceInfo> enabledProviders = new HashSet<>();
+ synchronized (mLock) {
+ runForUser(
+ (service) -> {
+ enabledProviders.add(service.getCredentialProviderInfo().getServiceInfo());
+ });
}
-
- return services;
+ return enabledProviders;
}
@Override
@@ -828,11 +815,11 @@
}
private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
- return CredentialProviderInfo.getCredentialProviderServices(
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
userId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
+ CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+ new HashSet<>());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 546c48f..ee55a1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,7 +21,8 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
@@ -82,7 +83,7 @@
Log.i(TAG, "newServiceInfoLocked with null mInfo , "
+ serviceComponent.getPackageName());
}
- mInfo = new CredentialProviderInfo(
+ mInfo = CredentialProviderInfoFactory.create(
getContext(), serviceComponent,
mUserId, /*isSystemProvider=*/false);
return mInfo.getServiceInfo();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 2c6c0d8..546c37f 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.DisabledProviderData;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
@@ -31,11 +32,12 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -118,11 +120,11 @@
.map(ProviderData::getProviderFlattenedComponentName)
.collect(Collectors.toUnmodifiableSet());
Set<String> allProviders =
- CredentialProviderInfo.getCredentialProviderServices(
+ CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
mUserId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)
+ CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
+ new HashSet<>())
.stream()
.map(CredentialProviderInfo::getServiceInfo)
.map(ServiceInfo::getComponentName)
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 13f4b54..8c6e5ce 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,12 +16,10 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
@@ -31,11 +29,11 @@
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -95,11 +93,11 @@
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
}
@@ -120,18 +118,18 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -143,7 +141,7 @@
}
if (isSessionCancelled()) {
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -160,10 +158,10 @@
private void logFailureOrUserCancel(String errorType) {
if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index f75a9b6..91470f6 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -16,12 +16,6 @@
package com.android.server.credentials;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -43,20 +37,8 @@
private static final String TAG = "MetricUtilities";
- private static final int DEFAULT_INT_32 = -1;
- private static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
-
- // Metrics constants TODO(b/269290341) migrate to enums eventually to improve
- protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_UNKNOWN =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+ public static final int DEFAULT_INT_32 = -1;
+ public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
/**
@@ -80,6 +62,21 @@
}
/**
+ * Given any two timestamps in nanoseconds, this gets the difference and converts to
+ * milliseconds. Assumes the difference is not larger than the maximum int size.
+ *
+ * @param t2 the final timestamp
+ * @param t1 the initial timestamp
+ * @return the timestamp difference converted to microseconds
+ */
+ protected static int getMetricTimestampDifferenceMicroseconds(long t2, long t1) {
+ if (t2 - t1 > Integer.MAX_VALUE) {
+ throw new ArithmeticException("Input timestamps are too far apart and unsupported");
+ }
+ return (int) ((t2 - t1) / 1000);
+ }
+
+ /**
* The most common logging helper, handles the overall status of the API request with the
* provider status and latencies. Other versions of this method may be more useful depending
* on the situation, as this is geared towards the logging of {@link ProviderSession} types.
@@ -102,7 +99,7 @@
for (var session : providerSessions) {
CandidateProviderMetric metric = session.mCandidateProviderMetric;
candidateUidList[index] = metric.getCandidateUid();
- candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMs();
+ candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
candidateStatusList[index] = metric.getProviderQueryStatus();
index++;
}
@@ -116,9 +113,9 @@
/* repeated_candidate_provider_status */ candidateStatusList,
/* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
/* chosen_provider_round_trip_time_overall_microseconds */
- chosenProviderMetric.getEntireProviderLatencyMs(),
- /* chosen_provider_final_phase_microseconds */
- chosenProviderMetric.getFinalPhaseLatencyMs(),
+ chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+ /* chosen_provider_final_phase_microseconds (backwards compat only) */
+ DEFAULT_INT_32,
/* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index ce9fca7..b7a4cd5 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -20,11 +20,11 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.credentials.ClearCredentialStateException;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Slog;
@@ -119,8 +119,8 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 370d21f..640cc33 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
@@ -33,7 +34,6 @@
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
import android.util.Log;
@@ -226,8 +226,8 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 80a61b9..07e2f87 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.AuthenticationEntry;
@@ -35,7 +36,6 @@
import android.service.credentials.BeginGetCredentialResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialRequest;
import android.service.credentials.RemoteEntry;
@@ -268,8 +268,8 @@
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
@@ -502,8 +502,7 @@
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry
- .getBeginGetCredentialOption().getId()));
+ setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 53ed070..a857695 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -16,9 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,14 +24,15 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.credentials.Credential;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.CandidateProviderMetric;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.UUID;
@@ -205,9 +203,11 @@
mCandidateProviderMetric
.setQueryFinishTimeNanoseconds(System.nanoTime());
if (isTerminatingStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
+ .getMetricCode());
} else if (isCompletionStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
+ .getMetricCode());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 86e05cf..c1f35d0 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -22,6 +22,7 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
@@ -30,7 +31,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.internal.R;
@@ -78,7 +78,8 @@
//TODO improve design to allow grouped metrics per request
protected final String mHybridService;
- @NonNull protected RequestSessionStatus mRequestSessionStatus =
+ @NonNull
+ protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
/** The status in which a given request session is. */
@@ -213,6 +214,7 @@
/**
* Called by RequestSession's upon chosen metric determination.
+ *
* @param componentName the componentName to associate with a provider
*/
protected void setChosenMetric(ComponentName componentName) {
@@ -220,8 +222,8 @@
.mCandidateProviderMetric;
mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
- mChosenProviderMetric.setQueryFinishTimeNanoseconds(
- metric.getQueryFinishTimeNanoseconds());
- mChosenProviderMetric.setStartTimeNanoseconds(metric.getStartTimeNanoseconds());
+ mChosenProviderMetric.setQueryPhaseLatencyMicroseconds(
+ metric.getQueryLatencyMicroseconds());
+ mChosenProviderMetric.setQueryStartTimeNanoseconds(metric.getStartQueryTimeNanoseconds());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index 36a1f2d..22cab70 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -22,11 +22,11 @@
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED;
public enum ApiStatus {
- METRICS_API_STATUS_SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
- METRICS_API_STATUS_FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
- METRICS_API_STATUS_CLIENT_CANCELED(
+ SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
+ FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
+ CLIENT_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED),
- METRICS_API_STATUS_USER_CANCELED(
+ USER_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED);
private final int mInnerMetricCode;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
index acfb4a4..9f438ec 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
@@ -18,63 +18,72 @@
/**
* The central candidate provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
*/
public class CandidateProviderMetric {
+ private static final String TAG = "CandidateProviderMetric";
private int mCandidateUid = -1;
- private long mStartTimeNanoseconds = -1;
+
+ // Raw timestamp in nanoseconds, will be converted to microseconds for logging
+
+ private long mStartQueryTimeNanoseconds = -1;
private long mQueryFinishTimeNanoseconds = -1;
private int mProviderQueryStatus = -1;
- public CandidateProviderMetric(long startTime, long queryFinishTime, int providerQueryStatus,
- int candidateUid) {
- this.mStartTimeNanoseconds = startTime;
- this.mQueryFinishTimeNanoseconds = queryFinishTime;
- this.mProviderQueryStatus = providerQueryStatus;
- this.mCandidateUid = candidateUid;
+ public CandidateProviderMetric() {
}
- public CandidateProviderMetric(){}
+ /* ---------- Latencies ---------- */
- public void setStartTimeNanoseconds(long startTimeNanoseconds) {
- this.mStartTimeNanoseconds = startTimeNanoseconds;
+ public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) {
+ this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds;
}
public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
}
- public void setProviderQueryStatus(int providerQueryStatus) {
- this.mProviderQueryStatus = providerQueryStatus;
- }
-
- public void setCandidateUid(int candidateUid) {
- this.mCandidateUid = candidateUid;
- }
-
- public long getStartTimeNanoseconds() {
- return this.mStartTimeNanoseconds;
+ public long getStartQueryTimeNanoseconds() {
+ return this.mStartQueryTimeNanoseconds;
}
public long getQueryFinishTimeNanoseconds() {
return this.mQueryFinishTimeNanoseconds;
}
+ /**
+ * Returns the latency in microseconds for the query phase.
+ */
+ public int getQueryLatencyMicroseconds() {
+ return (int) ((this.getQueryFinishTimeNanoseconds()
+ - this.getStartQueryTimeNanoseconds()) / 1000);
+ }
+
+ // TODO (in direct next dependent CL, so this is transient) - add reference timestamp in micro
+ // seconds for this too.
+
+ /* ------------- Provider Query Status ------------ */
+
+ public void setProviderQueryStatus(int providerQueryStatus) {
+ this.mProviderQueryStatus = providerQueryStatus;
+ }
+
public int getProviderQueryStatus() {
return this.mProviderQueryStatus;
}
+ /* -------------- Candidate Uid ---------------- */
+
+ public void setCandidateUid(int candidateUid) {
+ this.mCandidateUid = candidateUid;
+ }
+
public int getCandidateUid() {
return this.mCandidateUid;
}
-
- /**
- * Returns the latency in microseconds for the query phase.
- */
- public int getQueryLatencyMs() {
- return (int) ((this.getQueryFinishTimeNanoseconds()
- - this.getStartTimeNanoseconds()) / 1000);
- }
-
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
index c4d0b3c..0310255 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
@@ -16,18 +16,42 @@
package com.android.server.credentials.metrics;
+import android.util.Log;
+
+import com.android.server.credentials.MetricUtilities;
+
/**
* The central chosen provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
*/
public class ChosenProviderMetric {
+ // TODO(b/270403549) - applies elsewhere, likely removed or replaced with a count-index (1,2,3)
+ private static final String TAG = "ChosenProviderMetric";
private int mChosenUid = -1;
- private long mStartTimeNanoseconds = -1;
- private long mQueryFinishTimeNanoseconds = -1;
+
+ // Latency figures typically fed in from prior CandidateProviderMetric
+
+ private int mPreQueryPhaseLatencyMicroseconds = -1;
+ private int mQueryPhaseLatencyMicroseconds = -1;
+
+ // Timestamps kept in raw nanoseconds. Expected to be converted to microseconds from using
+ // reference 'mServiceBeganTimeNanoseconds' during metric log point.
+
+ private long mServiceBeganTimeNanoseconds = -1;
+ private long mQueryStartTimeNanoseconds = -1;
+ private long mUiCallStartTimeNanoseconds = -1;
+ private long mUiCallEndTimeNanoseconds = -1;
private long mFinalFinishTimeNanoseconds = -1;
private int mChosenProviderStatus = -1;
- public ChosenProviderMetric() {}
+ public ChosenProviderMetric() {
+ }
+
+ /* ------------------- UID ------------------- */
public int getChosenUid() {
return mChosenUid;
@@ -37,30 +61,138 @@
mChosenUid = chosenUid;
}
- public long getStartTimeNanoseconds() {
- return mStartTimeNanoseconds;
+ /* ---------------- Latencies ------------------ */
+
+
+ /* ----- Direct Latencies ------- */
+
+ /**
+ * In order for a chosen provider to be selected, the call must have successfully begun.
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial latency figure into
+ * this chosen provider metric.
+ *
+ * @param preQueryPhaseLatencyMicroseconds the millisecond latency for the service start,
+ * typically passed in through the
+ * {@link InitialPhaseMetric}
+ */
+ public void setPreQueryPhaseLatencyMicroseconds(int preQueryPhaseLatencyMicroseconds) {
+ mPreQueryPhaseLatencyMicroseconds = preQueryPhaseLatencyMicroseconds;
}
- public void setStartTimeNanoseconds(long startTimeNanoseconds) {
- mStartTimeNanoseconds = startTimeNanoseconds;
+ /**
+ * In order for a chosen provider to be selected, a candidate provider must exist. The
+ * candidate provider can directly pass the final latency figure into this chosen provider
+ * metric.
+ *
+ * @param queryPhaseLatencyMicroseconds the millisecond latency for the query phase, typically
+ * passed in through the {@link CandidateProviderMetric}
+ */
+ public void setQueryPhaseLatencyMicroseconds(int queryPhaseLatencyMicroseconds) {
+ mQueryPhaseLatencyMicroseconds = queryPhaseLatencyMicroseconds;
}
- public long getQueryFinishTimeNanoseconds() {
- return mQueryFinishTimeNanoseconds;
+ public int getPreQueryPhaseLatencyMicroseconds() {
+ return mPreQueryPhaseLatencyMicroseconds;
}
- public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
- mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
+ public int getQueryPhaseLatencyMicroseconds() {
+ return mQueryPhaseLatencyMicroseconds;
+ }
+
+ public int getUiPhaseLatencyMicroseconds() {
+ return (int) ((this.mUiCallEndTimeNanoseconds
+ - this.mUiCallStartTimeNanoseconds) / 1000);
+ }
+
+ /**
+ * Returns the full provider (invocation to response) latency in microseconds. Expects the
+ * start time to be provided, such as from {@link CandidateProviderMetric}.
+ */
+ public int getEntireProviderLatencyMicroseconds() {
+ return (int) ((this.mFinalFinishTimeNanoseconds
+ - this.mQueryStartTimeNanoseconds) / 1000);
+ }
+
+ /**
+ * Returns the full (platform invoked to response) latency in microseconds. Expects the
+ * start time to be provided, such as from {@link InitialPhaseMetric}.
+ */
+ public int getEntireLatencyMicroseconds() {
+ return (int) ((this.mFinalFinishTimeNanoseconds
+ - this.mServiceBeganTimeNanoseconds) / 1000);
+ }
+
+ /* ----- Timestamps for Latency ----- */
+
+ /**
+ * In order for a chosen provider to be selected, the call must have successfully begun.
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial timestamp into this
+ * chosen provider metric.
+ *
+ * @param serviceBeganTimeNanoseconds the timestamp moment when the platform was called,
+ * typically passed in through the {@link InitialPhaseMetric}
+ */
+ public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) {
+ mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds;
+ }
+
+ public void setQueryStartTimeNanoseconds(long queryStartTimeNanoseconds) {
+ mQueryStartTimeNanoseconds = queryStartTimeNanoseconds;
+ }
+
+ public void setUiCallStartTimeNanoseconds(long uiCallStartTimeNanoseconds) {
+ this.mUiCallStartTimeNanoseconds = uiCallStartTimeNanoseconds;
+ }
+
+ public void setUiCallEndTimeNanoseconds(long uiCallEndTimeNanoseconds) {
+ this.mUiCallEndTimeNanoseconds = uiCallEndTimeNanoseconds;
+ }
+
+ public void setFinalFinishTimeNanoseconds(long finalFinishTimeNanoseconds) {
+ mFinalFinishTimeNanoseconds = finalFinishTimeNanoseconds;
+ }
+
+ public long getServiceBeganTimeNanoseconds() {
+ return mServiceBeganTimeNanoseconds;
+ }
+
+ public long getQueryStartTimeNanoseconds() {
+ return mQueryStartTimeNanoseconds;
+ }
+
+ public long getUiCallStartTimeNanoseconds() {
+ return mUiCallStartTimeNanoseconds;
+ }
+
+ public long getUiCallEndTimeNanoseconds() {
+ return mUiCallEndTimeNanoseconds;
}
public long getFinalFinishTimeNanoseconds() {
return mFinalFinishTimeNanoseconds;
}
- public void setFinalFinishTimeNanoseconds(long finalFinishTimeNanoseconds) {
- mFinalFinishTimeNanoseconds = finalFinishTimeNanoseconds;
+ /* --- Time Stamp Conversion to Microseconds --- */
+
+ /**
+ * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope
+ * of our logging timeframe, and size considerations of the metric, we require these to give us
+ * the microsecond timestamps from the start reference point.
+ *
+ * @param specificTimestamp the timestamp to consider, must be greater than the reference
+ * @return the microsecond integer timestamp from service start to query began
+ */
+ public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
+ if (specificTimestamp < this.mServiceBeganTimeNanoseconds) {
+ Log.i(TAG, "The timestamp is before service started, falling back to default int");
+ return MetricUtilities.DEFAULT_INT_32;
+ }
+ return (int) ((specificTimestamp
+ - this.mServiceBeganTimeNanoseconds) / 1000);
}
+ /* ----------- Provider Status -------------- */
+
public int getChosenProviderStatus() {
return mChosenProviderStatus;
}
@@ -68,23 +200,4 @@
public void setChosenProviderStatus(int chosenProviderStatus) {
mChosenProviderStatus = chosenProviderStatus;
}
-
- /**
- * Returns the full provider (invocation to response) latency in microseconds.
- */
- public int getEntireProviderLatencyMs() {
- return (int) ((this.getFinalFinishTimeNanoseconds()
- - this.getStartTimeNanoseconds()) / 1000);
- }
-
- // TODO get post click final phase and re-add the query phase time to metric
-
- /**
- * Returns the end of query to response phase latency in microseconds.
- */
- public int getFinalPhaseLatencyMs() {
- return (int) ((this.getFinalFinishTimeNanoseconds()
- - this.getQueryFinishTimeNanoseconds()) / 1000);
- }
-
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
new file mode 100644
index 0000000..5f062b0
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+/**
+ * This handles metrics collected prior to any remote calls to providers.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
+ */
+public class InitialPhaseMetric {
+ private static final String TAG = "PreCandidateMetric";
+
+ // The api being called, default set to unknown
+ private int mApiName = ApiName.UNKNOWN.getMetricCode();
+ // The caller uid of the calling application, default to -1
+ private int mCallerUid = -1;
+ // The session id to unite multiple atom emits, default to -1
+ private long mSessionId = -1;
+ // A sequence id to order united emits, default to -1
+ private int mSequenceId = -1;
+ private int mCountRequestClassType = -1;
+
+ // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
+ // reference point.
+ private long mCredentialServiceStartedTimeNanoseconds = -1;
+
+ // A reference point to give this object utility to capture latency. Can be directly handed
+ // over to the next latency object.
+ private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
+
+
+ public InitialPhaseMetric() {
+ }
+
+ /* ---------- Latencies ---------- */
+
+ /* -- Direct Latencies -- */
+
+ public int getServiceStartToQueryLatencyMicroseconds() {
+ return (int) ((this.mCredentialServiceStartedTimeNanoseconds
+ - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
+ }
+
+ /* -- Timestamps -- */
+
+ public void setCredentialServiceStartedTimeNanoseconds(
+ long credentialServiceStartedTimeNanoseconds
+ ) {
+ this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
+ }
+
+ public void setCredentialServiceBeginQueryTimeNanoseconds(
+ long credentialServiceBeginQueryTimeNanoseconds) {
+ mCredentialServiceBeginQueryTimeNanoseconds = credentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ public long getCredentialServiceStartedTimeNanoseconds() {
+ return mCredentialServiceStartedTimeNanoseconds;
+ }
+
+ public long getCredentialServiceBeginQueryTimeNanoseconds() {
+ return mCredentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ /* ------ ApiName ------ */
+
+ public void setApiName(int apiName) {
+ mApiName = apiName;
+ }
+
+ public int getApiName() {
+ return mApiName;
+ }
+
+ /* ------ CallerUid ------ */
+
+ public void setCallerUid(int callerUid) {
+ mCallerUid = callerUid;
+ }
+
+ public int getCallerUid() {
+ return mCallerUid;
+ }
+
+ /* ------ SessionId ------ */
+
+ public void setSessionId(long sessionId) {
+ mSessionId = sessionId;
+ }
+
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ /* ------ SequenceId ------ */
+
+ public void setSequenceId(int sequenceId) {
+ mSequenceId = sequenceId;
+ }
+
+ public int getSequenceId() {
+ return mSequenceId;
+ }
+
+ /* ------ Count Request Class Types ------ */
+
+ public void setCountRequestClassType(int countRequestClassType) {
+ mCountRequestClassType = countRequestClassType;
+ }
+
+ public int getCountRequestClassType() {
+ return mCountRequestClassType;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
new file mode 100644
index 0000000..08f1afa
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+
+public enum ProviderStatusForMetrics {
+
+ UNKNOWN(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN),
+ FINAL_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE),
+ QUERY_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE),
+ FINAL_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS),
+ QUERY_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS);
+
+ private final int mInnerMetricCode;
+
+ ProviderStatusForMetrics(int innerMetricCode) {
+ this.mInnerMetricCode = innerMetricCode;
+ }
+
+ /**
+ * Gives the West-world version of the metric name.
+ *
+ * @return a code corresponding to the west world metric name
+ */
+ public int getMetricCode() {
+ return this.mInnerMetricCode;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 4351bc1..80100a9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -24,6 +24,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -52,6 +54,11 @@
@GuardedBy("mLock")
private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+ @GuardedBy("mLock")
+ private List<String> mLauncherShortcutOverrides =
+ new ArrayList<>();
+
+
/** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. */
private final AtomicBoolean mCanGrantSensorsPermissions = new AtomicBoolean(false);
@@ -122,6 +129,22 @@
mCanGrantSensorsPermissions.set(canGrant);
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ synchronized (mLock) {
+ return new ArrayList<>(mLauncherShortcutOverrides);
+ }
+ }
+
+ /**
+ * Sets a list of packages for which shortcuts should be replaced by their badged version.
+ */
+ public void setLauncherShortcutOverrides(List<String> launcherShortcutOverrides) {
+ synchronized (mLock) {
+ mLauncherShortcutOverrides = new ArrayList<>(launcherShortcutOverrides);
+ }
+ }
+
/** Dump content */
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
@@ -131,6 +154,8 @@
pw.println("Password quality: " + mPasswordQuality);
pw.println("Permission policy: " + mPermissionPolicy);
pw.println("Admin can grant sensors permission: " + mCanGrantSensorsPermissions.get());
+ pw.print("Shortcuts overrides: ");
+ pw.println(mLauncherShortcutOverrides);
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 400ee1d..a4e563b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3522,16 +3522,30 @@
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
-
final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
+ boolean isManagedSubscription;
+
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
preferentialNetworkServiceConfigs = owner != null
? owner.mPreferentialNetworkServiceConfigs
: List.of(PreferentialNetworkServiceConfig.DEFAULT);
+
+ isManagedSubscription = owner != null && owner.mManagedSubscriptionsPolicy != null
+ && owner.mManagedSubscriptionsPolicy.getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS;
}
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
+ if (isManagedSubscription) {
+ String defaultDialerPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultDialer);
+ String defaultSmsPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultSms);
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
+ }
+
startOwnerService(userId, "start-user");
if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
@@ -7614,6 +7628,7 @@
if (isWorkProfileTelephonyFlagEnabled()) {
clearManagedSubscriptionsPolicy();
+ clearLauncherShortcutOverrides();
updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
}
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
@@ -7631,6 +7646,10 @@
}
}
+ private void clearLauncherShortcutOverrides() {
+ mPolicyCache.setLauncherShortcutOverrides(new ArrayList<>());
+ }
+
private void updateTelephonyCrossProfileIntentFilters(int parentUserId, int profileUserId,
boolean enableWorkTelephony) {
try {
@@ -22755,12 +22774,30 @@
} else {
Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
}
+
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
} catch (RemoteException re) {
// shouldn't happen
Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
}
}
+ private void updateDialerAndSmsManagedShortcutsOverrideCache(
+ String defaultDialerPackageName, String defaultSmsPackageName) {
+
+ List<String> shortcutOverrides = new ArrayList<>();
+
+ if (defaultDialerPackageName != null) {
+ shortcutOverrides.add(defaultDialerPackageName);
+ }
+
+ if (defaultSmsPackageName != null) {
+ shortcutOverrides.add(defaultSmsPackageName);
+ }
+ mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides);
+ }
+
private void registerListenerToAssignSubscriptionsToUser(int userId) {
synchronized (mSubscriptionsChangedListenerLock) {
if (mSubscriptionsChangedListener != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3ca158d..194647fd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -120,7 +120,9 @@
} else {
mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
}
-
+ for (int userId : usersIds) {
+ mDeviceStateCache.setHasProfileOwner(userId, hasProfileOwner(userId));
+ }
} else {
mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
for (int userId : usersIds) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index cd5ac7bc..1731590 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -782,6 +782,34 @@
assertTrue(mIntf.isUidInForeground(UID));
}
+ @Test
+ public void testAppWidgetVisibleDoesntChangeUidState() {
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
+ @Test
+ public void testAppWidgetNotVisibleDoesntChangeUidState() {
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, false);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
public void testUidStateChangedCallback(int initialState, int finalState) {
int initialUidState = processStateToUidState(initialState);
int finalUidState = processStateToUidState(finalState);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 7149265..6861c2f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -108,6 +108,7 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 85a2446..375b52d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -21,6 +21,7 @@
import static android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.content.ComponentName;
import android.os.IpcDataCache;
@@ -43,6 +44,7 @@
@RunWith(AndroidJUnit4.class)
public class OwnersTest extends DpmTestBase {
+ private static final int TEST_PO_USER = 10;
private static final String TESTDPC_PACKAGE = "com.afwsamples.testdpc";
private final DeviceStateCacheImpl mDeviceStateCache = new DeviceStateCacheImpl();
@@ -55,11 +57,11 @@
@Test
public void loadProfileOwner() throws Exception {
- getServices().addUsers(10);
+ getServices().addUsers(TEST_PO_USER);
final Owners owners = makeOwners();
- DpmTestUtils.writeToFile(owners.getProfileOwnerFile(10),
+ DpmTestUtils.writeToFile(owners.getProfileOwnerFile(TEST_PO_USER),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/profile_owner_1.xml"));
owners.load();
@@ -71,6 +73,9 @@
assertThat(owners.getProfileOwnerComponent(10))
.isEqualTo(new ComponentName(TESTDPC_PACKAGE,
"com.afwsamples.testdpc.DeviceAdminReceiver"));
+
+ assertWithMessage("Profile owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(TEST_PO_USER)).isTrue();
}
@Test
@@ -90,6 +95,10 @@
"com.afwsamples.testdpc.DeviceAdminReceiver"));
assertThat(owners.getSystemUpdatePolicy().getPolicyType()).isEqualTo(TYPE_INSTALL_WINDOWED);
+
+ assertWithMessage("Device owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(owners.getDeviceOwnerUserId()))
+ .isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index de1c219..94d30bb 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -24,6 +24,8 @@
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+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.assertNotEquals;
@@ -32,8 +34,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -61,10 +65,13 @@
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjectionManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -158,25 +165,40 @@
}
};
- class BasicInjector extends DisplayManagerService.Injector {
- @Override
- VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
- }
+ class BasicInjector extends DisplayManagerService.Injector {
+ @Override
+ IMediaProjectionManager getProjectionService() {
+ return mMockProjectionService;
+ }
- @Override
- LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new LocalDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener, new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
- }
+ @Override
+ VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure,
+ float requestedRefreshRate) {
+ return mMockDisplayToken;
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ }
+ });
+ }
+
+ @Override
+ LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new LocalDisplayAdapter(syncRoot, context, handler,
+ displayAdapterListener, new LocalDisplayAdapter.Injector() {
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
+ }
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
@@ -198,6 +220,7 @@
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+ @Mock IMediaProjectionManager mMockProjectionService;
@Mock IVirtualDeviceManager mIVirtualDeviceManager;
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
@@ -285,6 +308,7 @@
builder.setFlags(flags);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -410,6 +434,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -446,6 +471,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -479,6 +505,7 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -720,6 +747,7 @@
builder.setUniqueId(uniqueId);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -731,6 +759,7 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -768,6 +797,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. This should be added to the previously created display
@@ -783,6 +813,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertEquals(
@@ -820,6 +851,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
@@ -838,6 +870,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertNotEquals(
@@ -881,6 +914,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -906,6 +940,7 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -929,6 +964,7 @@
null /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is not set.
assertEquals(
@@ -960,6 +996,7 @@
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -971,6 +1008,7 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -985,6 +1023,54 @@
Display.INVALID_DISPLAY);
}
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(true);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession true");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(false);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
+ }
+
/**
* Tests that the virtual display is created with
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
@@ -1011,6 +1097,7 @@
builder.setUniqueId(uniqueId);
final int displayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -1043,6 +1130,7 @@
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
@@ -1093,7 +1181,6 @@
registerDefaultDisplays(displayManager);
- DisplayManagerService.BinderService bs = displayManager.new BinderService();
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
@@ -1111,6 +1198,7 @@
int displayId = localService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 178670e..397d7b5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -21,7 +21,6 @@
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
-import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -66,6 +65,7 @@
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
+ final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS;
final Set<Long> timestamps = new HashSet<>();
final long firstWakeup = 453192;
@@ -73,22 +73,21 @@
obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ);
timestamps.add(firstWakeup);
for (int i = 1; i < 1000; i++) {
- final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS);
+ final long delta = mRandom.nextLong(retention);
if (timestamps.add(firstWakeup + delta)) {
obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ);
}
}
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
- obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231,
+ obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231,
KERNEL_REASON_UNKNOWN_IRQ);
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
for (int i = 0; i < 100; i++) {
- final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS);
+ final long now = mRandom.nextLong(retention + 1, 100 * retention);
obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ);
- assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS))
- .isLessThan(0);
+ assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
}
}
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
index 2c5fcb8..68067d2 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -191,9 +191,9 @@
public void reset(long transactionId) {
try {
synchronized(mGadgetProxyLock) {
- if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy);
gadgetProxy.reset();
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 918ae79..bf12b9c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7608,6 +7608,55 @@
public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+ /**
+ * This values indicates that the cross SIM redialing timer shall be disabled.
+ *
+ * @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @hide
+ */
+ public static final int REDIAL_TIMER_DISABLED = 0;
+
+ /**
+ * A timer to guard the max attempting time on current SIM slot so that modem will not
+ * stuck in current SIM slot for long time. On timer expiry, if emergency call on the
+ * other SIM slot is preferable, telephony shall cancel the emergency call and place the
+ * call on the other SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then
+ * the timer will never be started and domain selection continues on the current SIM slot.
+ * This value should be greater than the value of {@link #KEY_EMERGENCY_SCAN_TIMER_SEC_INT}.
+ *
+ * The default value for the timer is 120 seconds.
+ * @hide
+ */
+ public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "cross_stack_redial_timer_sec_int";
+
+ /**
+ * If emergency calls are only allowed with normal-registered service and UE should get
+ * normal service in a short time with acquired band information, telephony
+ * expects dialing emergency call will be completed in a short time.
+ * If dialing is not completed with in a certain timeout, telephony shall place on
+ * another SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then the timer
+ * will never be started and domain selection continues on the current SIM slot.
+ * The timer shall be started for the first trial of each subscription and shall be ignored
+ * in the roaming networks and non-domestic networks.
+ *
+ * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
+ * @hide
+ */
+ public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "quick_cross_stack_redial_timer_sec_int";
+
+ /**
+ * Indicates whether the quick cross stack redial timer will be triggered only when
+ * the device is registered to the network.
+ *
+ * The default value is {@code true}.
+ * @hide
+ */
+ public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL =
+ KEY_PREFIX + "start_quick_cross_stack_redial_timer_when_registered_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7674,6 +7723,10 @@
defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
new String[0]);
+ defaults.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 120);
+ defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
+ defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
+ true);
return defaults;
}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 3b4cf75..1cfd22c 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -235,6 +235,23 @@
/** Call failed/dropped because of a network detach. */
public static final int NETWORK_DETACH = 261;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * If there is no other subscription available, the call may be redialed
+ * on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_TEMP_FAILURE = 325;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * Even if there is no other subscription available, the call should not
+ * be redialed on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_PERM_FAILURE = 326;
+
/** Mobile station (MS) is locked until next power cycle. */
public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
/** Drop call. */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 19ecf6a..05abf9f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -53,7 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
+open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
new file mode 100644
index 0000000..46899f3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.common.NavBar
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ // TAPL fails on landscape mode b/240916028
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_3BUTTON)
+ )
+ }
+ }
+}
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
index da98523..b848e63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -93,8 +93,7 @@
.then()
// Animation starts, but the app may not be drawn yet which means the Splash
// may be visible.
- .isInvisible(testApp, isOptional = true)
- .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .isSplashScreenVisibleFor(testApp, isOptional = true)
.then()
// App shows up with the custom animation starting at alpha=1.
.isVisible(testApp)