diff options
281 files changed, 6197 insertions, 2236 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 170cac43764a..50524998d416 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -48,7 +48,6 @@ stubs_defaults { ":opt-telephony-srcs", ":opt-net-voip-srcs", ":art-module-public-api-stubs-source", - ":conscrypt.module.public.api.stubs.source", ":android_icu4j_public_api_files", ], // TODO(b/147699819): remove below aidl includes. @@ -69,7 +68,10 @@ stubs_defaults { stubs_defaults { name: "metalava-full-api-stubs-default", defaults: ["metalava-base-api-stubs-default"], - srcs: [":framework-updatable-sources"], + srcs: [ + ":conscrypt.module.public.api.stubs.source", + ":framework-updatable-sources", + ], sdk_version: "core_platform", } @@ -110,7 +112,7 @@ droidstubs { }, last_released: { api_file: ":android.api.public.latest", - removed_api_file: "api/removed.txt", + removed_api_file: ":removed.api.public.latest", baseline_file: ":public-api-incompatibilities-with-last-released", }, api_lint: { @@ -152,7 +154,7 @@ droidstubs { }, last_released: { api_file: ":android.api.system.latest", - removed_api_file: "api/system-removed.txt", + removed_api_file: ":removed.api.system.latest", baseline_file: ":system-api-incompatibilities-with-last-released" }, api_lint: { @@ -216,7 +218,7 @@ droidstubs { }, last_released: { api_file: ":android.api.module-lib.latest", - removed_api_file: "api/module-lib-removed.txt", + removed_api_file: ":removed.api.module-lib.latest", baseline_file: ":module-lib-api-incompatibilities-with-last-released" }, api_lint: { diff --git a/apct-tests/perftests/core/AndroidTest.xml b/apct-tests/perftests/core/AndroidTest.xml index 478cfc1fe811..1b289130124f 100644 --- a/apct-tests/perftests/core/AndroidTest.xml +++ b/apct-tests/perftests/core/AndroidTest.xml @@ -25,9 +25,4 @@ <option name="package" value="com.android.perftests.core" /> <option name="hidden-api-checks" value="false"/> </test> - - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="directory-keys" value="/data/local/CorePerfTests" /> - <option name="collect-on-run-ended-only" value="true" /> - </metrics_collector> </configuration> diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp new file mode 100644 index 000000000000..f02cbcfc4daf --- /dev/null +++ b/apct-tests/perftests/windowmanager/Android.bp @@ -0,0 +1,26 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "WmPerfTests", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "androidx.annotation_annotation", + "apct-perftests-utils", + ], + test_suites: ["device-tests"], + platform_apis: true, + certificate: "platform", +} diff --git a/apct-tests/perftests/windowmanager/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml new file mode 100644 index 000000000000..7198176f62a0 --- /dev/null +++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.wm"> + + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.perftests.utils.PerfTestActivity"> + <intent-filter> + <action android:name="com.android.perftests.core.PERFTEST" /> + </intent-filter> + </activity> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.wm"/> +</manifest> diff --git a/apct-tests/perftests/windowmanager/AndroidTest.xml b/apct-tests/perftests/windowmanager/AndroidTest.xml new file mode 100644 index 000000000000..69d187f9419a --- /dev/null +++ b/apct-tests/perftests/windowmanager/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs WmPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="WmPerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.wm" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/data/local/WmPerfTests" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> +</configuration> diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java index 4ed3b4e09d11..4ed3b4e09d11 100644 --- a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 836e6b617395..1667c1658a07 100644 --- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -46,6 +46,7 @@ import androidx.test.runner.lifecycle.Stage; import org.junit.AfterClass; import org.junit.Assume; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -72,6 +73,12 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { private long mMeasuredTimeNs; + /** + * Used to skip each test method if there is error. It cannot be raised in static setup because + * that will break the amount of target method count. + */ + private static Exception sSetUpClassException; + @Parameterized.Parameter(0) public int intervalBetweenOperations; @@ -107,15 +114,21 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { sRecentsIntent = new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent); } catch (Exception e) { - Assume.assumeNoException(e); + sSetUpClassException = e; } } @AfterClass public static void tearDownClass() { + sSetUpClassException = null; sUiAutomation.dropShellPermissionIdentity(); } + @Before + public void setUp() { + Assume.assumeNoException(sSetUpClassException); + } + /** Simulate the timing of touch. */ private void makeInterval() { SystemClock.sleep(intervalBetweenOperations); diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index 8139a2e963c5..8139a2e963c5 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java diff --git a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java index c72cc9d635e0..c72cc9d635e0 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java index 9e17e940a06b..9e17e940a06b 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java diff --git a/apex/Android.bp b/apex/Android.bp index 67cd0d7fcd1e..51e030bd174d 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -74,6 +74,9 @@ stubs_defaults { api_file: "api/current.txt", removed_api_file: "api/removed.txt", }, + api_lint: { + enabled: true, + }, }, dist: { targets: ["sdk", "win_sdk"], @@ -93,6 +96,9 @@ stubs_defaults { api_file: "api/system-current.txt", removed_api_file: "api/system-removed.txt", }, + api_lint: { + enabled: true, + }, }, dist: { targets: ["sdk", "win_sdk"], @@ -147,6 +153,9 @@ stubs_defaults { api_file: "api/module-lib-current.txt", removed_api_file: "api/module-lib-removed.txt", }, + api_lint: { + enabled: true, + }, }, dist: { targets: ["sdk", "win_sdk"], @@ -173,6 +182,9 @@ stubs_defaults { api_file: "api/current.txt", removed_api_file: "api/removed.txt", }, + api_lint: { + enabled: true, + }, }, dist: { targets: ["sdk", "win_sdk"], diff --git a/apex/extservices/Android.bp b/apex/extservices/Android.bp index 68350afdac85..0c6c4c23dce1 100644 --- a/apex/extservices/Android.bp +++ b/apex/extservices/Android.bp @@ -21,7 +21,7 @@ apex { apex_defaults { name: "com.android.extservices-defaults", updatable: true, - min_sdk_version: "R", + min_sdk_version: "current", key: "com.android.extservices.key", certificate: ":com.android.extservices.certificate", apps: ["ExtServices"], diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 6d9e3eddf616..887d82c6413f 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -45,6 +45,14 @@ public interface AppStandbyInternal { boolean idle, int bucket, int reason); /** + * Callback to inform listeners that the parole state has changed. This means apps are + * allowed to do work even if they're idle or in a low bucket. + */ + public void onParoleStateChanged(boolean isParoleOn) { + // No-op by default + } + + /** * Optional callback to inform the listener that the app has transitioned into * an active state due to user interaction. */ @@ -92,6 +100,11 @@ public interface AppStandbyInternal { boolean isAppIdleFiltered(String packageName, int appId, int userId, long elapsedRealtime); + /** + * @return true if currently app idle parole mode is on. + */ + boolean isInParole(); + int[] getIdleUidsForUser(int userId); void setAppIdleAsync(String packageName, boolean idle, int userId); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 24728dd8edca..cb5cb175ff24 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -214,8 +214,7 @@ public class AppStandbyController implements AppStandbyInternal { private AppIdleHistory mAppIdleHistory; @GuardedBy("mPackageAccessListeners") - private ArrayList<AppIdleStateChangeListener> - mPackageAccessListeners = new ArrayList<>(); + private final ArrayList<AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>(); /** Whether we've queried the list of carrier privileged apps. */ @GuardedBy("mAppIdleLock") @@ -235,6 +234,7 @@ public class AppStandbyController implements AppStandbyInternal { static final int MSG_FORCE_IDLE_STATE = 4; static final int MSG_CHECK_IDLE_STATES = 5; static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8; + static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; @@ -390,7 +390,16 @@ public class AppStandbyController implements AppStandbyInternal { @VisibleForTesting void setAppIdleEnabled(boolean enabled) { - mAppIdleEnabled = enabled; + synchronized (mAppIdleLock) { + if (mAppIdleEnabled != enabled) { + final boolean oldParoleState = isInParole(); + mAppIdleEnabled = enabled; + if (isInParole() != oldParoleState) { + postParoleStateChanged(); + } + } + } + } @Override @@ -563,11 +572,23 @@ public class AppStandbyController implements AppStandbyInternal { if (mIsCharging != isCharging) { if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging); mIsCharging = isCharging; + postParoleStateChanged(); } } } @Override + public boolean isInParole() { + return !mAppIdleEnabled || mIsCharging; + } + + private void postParoleStateChanged() { + if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED"); + mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED); + mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED); + } + + @Override public void postCheckIdleStates(int userId) { mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0)); } @@ -1502,6 +1523,15 @@ public class AppStandbyController implements AppStandbyInternal { } } + private void informParoleStateChanged() { + final boolean paroled = isInParole(); + synchronized (mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { + listener.onParoleStateChanged(paroled); + } + } + } + @Override public void flushToDisk(int userId) { synchronized (mAppIdleLock) { @@ -1920,6 +1950,11 @@ public class AppStandbyController implements AppStandbyInternal { args.recycle(); break; + case MSG_PAROLE_STATE_CHANGED: + if (DEBUG) Slog.d(TAG, "Parole state: " + isInParole()); + informParoleStateChanged(); + break; + case MSG_CHECK_PACKAGE_IDLE_STATE: checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2, mInjector.elapsedRealtime()); diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index a1c886a26562..3bc4f7b0ab72 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -102,6 +102,15 @@ droidstubs { "framework-media-stubs-srcs-defaults", "framework-module-stubs-defaults-publicapi", ], + check_api: { + last_released: { + api_file: ":framework-media.api.public.latest", + removed_api_file: ":framework-media-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-media.api.public.latest", + }, + }, } droidstubs { @@ -110,6 +119,15 @@ droidstubs { "framework-media-stubs-srcs-defaults", "framework-module-stubs-defaults-systemapi", ], + check_api: { + last_released: { + api_file: ":framework-media.api.system.latest", + removed_api_file: ":framework-media-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-media.api.system.latest", + }, + }, } droidstubs { @@ -118,6 +136,15 @@ droidstubs { "framework-media-stubs-srcs-defaults", "framework-module-api-defaults-module_libs_api", ], + check_api: { + last_released: { + api_file: ":framework-media.api.module-lib.latest", + removed_api_file: ":framework-media-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-media.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 3119b7d29b36..68c27a8327cb 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -55,6 +55,15 @@ droidstubs { "framework-module-stubs-defaults-publicapi", "framework-permission-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-permission.api.public.latest", + removed_api_file: ":framework-permission-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-permission.api.public.latest", + }, + }, } droidstubs { @@ -63,6 +72,15 @@ droidstubs { "framework-module-stubs-defaults-systemapi", "framework-permission-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-permission.api.system.latest", + removed_api_file: ":framework-permission-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-permission.api.system.latest", + }, + }, } droidstubs { @@ -71,6 +89,15 @@ droidstubs { "framework-module-api-defaults-module_libs_api", "framework-permission-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-permission.api.module-lib.latest", + removed_api_file: ":framework-permission-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-permission.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 2d92d00b6309..61449763540b 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -41,6 +41,15 @@ droidstubs { name: "service-permission-stubs-srcs", srcs: [ ":service-permission-sources" ], defaults: ["service-module-stubs-srcs-defaults"], + check_api: { + last_released: { + api_file: ":service-permission.api.system-server.latest", + removed_api_file: ":service-permission-removed.api.system-server.latest", + }, + api_lint: { + new_since: ":service-permission.api.system-server.latest", + }, + }, visibility: ["//visibility:private"], dist: { dest: "service-permission.txt" }, } diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 6a787116c005..14e23ed9a8a1 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -57,6 +57,15 @@ droidstubs { "framework-module-stubs-defaults-publicapi", "framework-sdkextensions-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-sdkextensions.api.public.latest", + removed_api_file: ":framework-sdkextensions-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-sdkextensions.api.public.latest", + }, + }, } droidstubs { @@ -65,6 +74,15 @@ droidstubs { "framework-module-stubs-defaults-systemapi", "framework-sdkextensions-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-sdkextensions.api.system.latest", + removed_api_file: ":framework-sdkextensions-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-sdkextensions.api.system.latest", + }, + }, } droidstubs { @@ -73,6 +91,15 @@ droidstubs { "framework-module-api-defaults-module_libs_api", "framework-sdkextensions-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-sdkextensions.api.module-lib.latest", + removed_api_file: ":framework-sdkextensions-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-sdkextensions.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 7d0f2ee274cd..9f5d933bb48a 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -93,6 +93,15 @@ droidstubs { "framework-module-stubs-defaults-publicapi", "framework-statsd-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-statsd.api.public.latest", + removed_api_file: ":framework-statsd-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-statsd.api.public.latest", + }, + }, } droidstubs { @@ -101,6 +110,15 @@ droidstubs { "framework-module-stubs-defaults-systemapi", "framework-statsd-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-statsd.api.system.latest", + removed_api_file: ":framework-statsd-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-statsd.api.system.latest", + }, + }, } droidstubs { @@ -109,6 +127,15 @@ droidstubs { "framework-module-api-defaults-module_libs_api", "framework-statsd-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-statsd.api.module-lib.latest", + removed_api_file: ":framework-statsd-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-statsd.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index 7fbfc4318949..d1b7d8dc2c7a 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -28,7 +28,6 @@ import android.os.Binder; import android.os.IPullAtomCallback; import android.os.IPullAtomResultReceiver; import android.os.IStatsManagerService; -import android.os.IStatsd; import android.os.RemoteException; import android.os.StatsFrameworkInitializer; import android.util.AndroidException; @@ -57,9 +56,6 @@ public final class StatsManager { private final Context mContext; @GuardedBy("sLock") - private IStatsd mService; - - @GuardedBy("sLock") private IStatsManagerService mStatsManagerService; /** diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 93e6c108a289..5cf5e0b1d182 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -54,11 +54,11 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * Helper service for statsd (the native stats management service in cmds/statsd/). @@ -112,17 +112,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final HashMap<Long, String> mDeletedFiles = new HashMap<>(); private final CompanionHandler mHandler; - // Flag that is set when PHASE_BOOT_COMPLETED is triggered in the StatsCompanion lifecycle. This - // and the flag mSentBootComplete below is used for synchronization to ensure that the boot - // complete signal is only ever sent once to statsd. Two signals are needed because - // #sayHiToStatsd can be called from both statsd and #onBootPhase - // PHASE_THIRD_PARTY_APPS_CAN_START. - @GuardedBy("sStatsdLock") - private boolean mBootCompleted = false; - // Flag that is set when IStatsd#bootCompleted is called. This flag ensures that boot complete - // signal is only ever sent once. - @GuardedBy("sStatsdLock") - private boolean mSentBootComplete = false; + // Flag that is set when PHASE_BOOT_COMPLETED is triggered in the StatsCompanion lifecycle. + private AtomicBoolean mBootCompleted = new AtomicBoolean(false); public StatsCompanionService(Context context) { super(); @@ -607,27 +598,35 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Statsd related code /** - * Fetches the statsd IBinder service. This is a blocking call. + * Fetches the statsd IBinder service. This is a blocking call that always refetches statsd + * instead of returning the cached sStatsd. * Note: This should only be called from {@link #sayHiToStatsd()}. All other clients should use * the cached sStatsd via {@link #getStatsdNonblocking()}. */ - private IStatsd fetchStatsdService(StatsdDeathRecipient deathRecipient) { - synchronized (sStatsdLock) { - if (sStatsd == null) { - sStatsd = IStatsd.Stub.asInterface(StatsFrameworkInitializer - .getStatsServiceManager() - .getStatsdServiceRegisterer() - .get()); - if (sStatsd != null) { - try { - sStatsd.asBinder().linkToDeath(deathRecipient, /* flags */ 0); - } catch (RemoteException e) { - Log.e(TAG, "linkToDeath(StatsdDeathRecipient) failed"); - statsdNotReadyLocked(); - } + private IStatsd fetchStatsdServiceLocked() { + sStatsd = IStatsd.Stub.asInterface(StatsFrameworkInitializer + .getStatsServiceManager() + .getStatsdServiceRegisterer() + .get()); + return sStatsd; + } + + private void registerStatsdDeathRecipient(IStatsd statsd, List<BroadcastReceiver> receivers) { + StatsdDeathRecipient deathRecipient = new StatsdDeathRecipient(statsd, receivers); + + try { + statsd.asBinder().linkToDeath(deathRecipient, /*flags=*/0); + } catch (RemoteException e) { + Log.e(TAG, "linkToDeath (StatsdDeathRecipient) failed"); + // Statsd has already died. Unregister receivers ourselves. + for (BroadcastReceiver receiver : receivers) { + mContext.unregisterReceiver(receiver); + } + synchronized (sStatsdLock) { + if (statsd == sStatsd) { + statsdNotReadyLocked(); } } - return sStatsd; } } @@ -648,22 +647,23 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * statsd. */ private void sayHiToStatsd() { - if (getStatsdNonblocking() != null) { - Log.e(TAG, "Trying to fetch statsd, but it was already fetched", - new IllegalStateException( - "sStatsd is not null when being fetched")); - return; + IStatsd statsd; + synchronized (sStatsdLock) { + if (sStatsd != null && sStatsd.asBinder().isBinderAlive()) { + Log.e(TAG, "statsd has already been fetched before", + new IllegalStateException("IStatsd object should be null or dead")); + return; + } + statsd = fetchStatsdServiceLocked(); } - StatsdDeathRecipient deathRecipient = new StatsdDeathRecipient(); - IStatsd statsd = fetchStatsdService(deathRecipient); + if (statsd == null) { - Log.i(TAG, - "Could not yet find statsd to tell it that StatsCompanion is " - + "alive."); + Log.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is alive."); return; } - mStatsManagerService.statsdReady(statsd); + if (DEBUG) Log.d(TAG, "Saying hi to statsd"); + mStatsManagerService.statsdReady(statsd); try { statsd.statsCompanionReady(); @@ -682,8 +682,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mContext.registerReceiverForAllUsers(appUpdateReceiver, filter, null, null); // Setup receiver for user initialize (which happens once for a new user) - // and - // if a user is removed. + // and if a user is removed. filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE); filter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiverForAllUsers(userUpdateReceiver, filter, null, null); @@ -691,27 +690,20 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Setup receiver for device reboots or shutdowns. filter = new IntentFilter(Intent.ACTION_REBOOT); filter.addAction(Intent.ACTION_SHUTDOWN); - mContext.registerReceiverForAllUsers( - shutdownEventReceiver, filter, null, null); + mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null, null); - // Only add the receivers if the registration is successful. - deathRecipient.addRegisteredBroadcastReceivers( - List.of(appUpdateReceiver, userUpdateReceiver, shutdownEventReceiver)); + // Register death recipient. + List<BroadcastReceiver> broadcastReceivers = + List.of(appUpdateReceiver, userUpdateReceiver, shutdownEventReceiver); + registerStatsdDeathRecipient(statsd, broadcastReceivers); - // Used so we can call statsd.bootComplete() outside of the lock. - boolean shouldSendBootComplete = false; - synchronized (sStatsdLock) { - if (mBootCompleted && !mSentBootComplete) { - mSentBootComplete = true; - shouldSendBootComplete = true; - } - } - if (shouldSendBootComplete) { + // Tell statsd that boot has completed. The signal may have already been sent, but since + // the signal-receiving function is idempotent, that's ok. + if (mBootCompleted.get()) { statsd.bootCompleted(); } - // Pull the latest state of UID->app name, version mapping when - // statsd starts. + // Pull the latest state of UID->app name, version mapping when statsd starts. informAllUids(mContext); Log.i(TAG, "Told statsd that StatsCompanionService is alive."); @@ -722,18 +714,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private class StatsdDeathRecipient implements IBinder.DeathRecipient { - private List<BroadcastReceiver> mReceiversToUnregister; - - StatsdDeathRecipient() { - mReceiversToUnregister = new ArrayList<>(); - } + private final IStatsd mStatsd; + private final List<BroadcastReceiver> mReceiversToUnregister; - public void addRegisteredBroadcastReceivers(List<BroadcastReceiver> receivers) { - synchronized (sStatsdLock) { - mReceiversToUnregister.addAll(receivers); - } + StatsdDeathRecipient(IStatsd statsd, List<BroadcastReceiver> receivers) { + mStatsd = statsd; + mReceiversToUnregister = receivers; } + // It is possible for binderDied to be called after a restarted statsd calls statsdReady, + // but that's alright because the code does not assume an ordering of the two calls. @Override public void binderDied() { Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); @@ -762,13 +752,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } } - // We only unregister in binder death becaseu receivers can only be unregistered - // once, or an IllegalArgumentException is thrown. + + // Unregister receivers on death because receivers can only be unregistered once. + // Otherwise, an IllegalArgumentException is thrown. for (BroadcastReceiver receiver: mReceiversToUnregister) { mContext.unregisterReceiver(receiver); } - statsdNotReadyLocked(); - mSentBootComplete = false; + + // It's possible for statsd to have restarted and called statsdReady, causing a new + // sStatsd binder object to be fetched, before the binderDied callback runs. Only + // call #statsdNotReadyLocked if that hasn't happened yet. + if (mStatsd == sStatsd) { + statsdNotReadyLocked(); + } } } } @@ -779,19 +775,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } void bootCompleted() { + mBootCompleted.set(true); IStatsd statsd = getStatsdNonblocking(); - synchronized (sStatsdLock) { - mBootCompleted = true; - if (mSentBootComplete) { - // do not send a boot complete a second time. - return; - } - if (statsd == null) { - // Statsd is not yet ready. - // Delay the boot completed ping to {@link #sayHiToStatsd()} - return; - } - mSentBootComplete = true; + if (statsd == null) { + // Statsd is not yet ready. + // Delay the boot completed ping to {@link #sayHiToStatsd()} + return; } try { statsd.bootCompleted(); @@ -808,8 +797,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } synchronized (sStatsdLock) { - writer.println( - "Number of configuration files deleted: " + mDeletedFiles.size()); + writer.println("Number of configuration files deleted: " + mDeletedFiles.size()); if (mDeletedFiles.size() > 0) { writer.println(" timestamp, deleted file name"); } @@ -817,8 +805,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { SystemClock.currentThreadTimeMillis() - SystemClock.elapsedRealtime(); for (Long elapsedMillis : mDeletedFiles.keySet()) { long deletionMillis = lastBootMillis + elapsedMillis; - writer.println( - " " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis)); + writer.println(" " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis)); } } } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 90764b0bd426..97846f2397a5 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -172,6 +172,10 @@ public class StatsManagerService extends IStatsManagerService.Stub { public void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis, int[] additiveFields, IPullAtomCallback pullerCallback) { enforceRegisterStatsPullAtomPermission(); + if (pullerCallback == null) { + Log.w(TAG, "Puller callback is null for atom " + atomTag); + return; + } int callingUid = Binder.getCallingUid(); PullerKey key = new PullerKey(callingUid, atomTag); PullerValue val = diff --git a/api/test-current.txt b/api/test-current.txt index cc3604ce728c..46049bd949c5 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -431,6 +431,7 @@ package android.app { } public class DreamManager { + method @RequiresPermission("android.permission.READ_DREAM_STATE") public boolean isDreaming(); method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void setActiveDream(@NonNull android.content.ComponentName); method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void startDream(@NonNull android.content.ComponentName); method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void stopDream(); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index bd9f7a59fcbd..a65f5f792daa 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -343,9 +343,11 @@ status_t StatsService::handleShellCommand(int in, int out, int err, const char** if (!utf8Args[0].compare(String8("print-logs"))) { return cmd_print_logs(out, utf8Args); } + if (!utf8Args[0].compare(String8("send-active-configs"))) { return cmd_trigger_active_config_broadcast(out, utf8Args); } + if (!utf8Args[0].compare(String8("data-subscribe"))) { { std::lock_guard<std::mutex> lock(mShellSubscriberMutex); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 650545f939cc..504890f6cf52 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3021,14 +3021,14 @@ message LauncherUIChanged { optional string component_name = 11; // (x, y) coordinate and the index information of the target on the container - optional int32 grid_x = 12; - optional int32 grid_y = 13; - optional int32 page_id = 14; + optional int32 grid_x = 12 [default = -1]; + optional int32 grid_y = 13 [default = -1]; + optional int32 page_id = 14 [default = -2]; // e.g., folder icon's (x, y) location and index information on the workspace - optional int32 grid_x_parent = 15; - optional int32 grid_y_parent = 16; - optional int32 page_id_parent = 17; + optional int32 grid_x_parent = 15 [default = -1]; + optional int32 grid_y_parent = 16 [default = -1]; + optional int32 page_id_parent = 17 [default = -2]; // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE optional int32 hierarchy = 18; @@ -3036,7 +3036,7 @@ message LauncherUIChanged { optional bool is_work_profile = 19; // Used to store the predicted rank of the target - optional int32 rank = 20; + optional int32 rank = 20 [default = -1]; // e.g., folderLabelState can be captured in the following two fields optional int32 from_state = 21; @@ -3044,6 +3044,9 @@ message LauncherUIChanged { // e.g., autofilled or suggested texts that are not user entered optional string edittext = 23; + + // e.g., number of contents inside a container (e.g., icons inside a folder) + optional int32 cardinality = 24; } /** @@ -3064,22 +3067,34 @@ message LauncherStaticLayout { optional string component_name = 6; // (x, y) coordinate and the index information of the target on the container - optional int32 grid_x = 7; - optional int32 grid_y = 8; - optional int32 page_id = 9; + optional int32 grid_x = 7 [default = -1]; + optional int32 grid_y = 8 [default = -1]; + optional int32 page_id = 9 [default = -2]; // e.g., folder icon's (x, y) location and index information on the workspace - optional int32 grid_x_parent = 10; - optional int32 grid_y_parent = 11; - optional int32 page_id_parent = 12; - - // e.g., WORKSPACE, HOTSEAT, FOLDER_WORKSPACE, FOLDER_HOTSEAT + // e.g., when used with widgets target, use these values for (span_x, span_y) + optional int32 grid_x_parent = 10 [default = -1]; + optional int32 grid_y_parent = 11 [default = -1]; + optional int32 page_id_parent = 12 [default = -2]; + + // UNKNOWN = 0 + // HOTSEAT = 1 + // WORKSPACE = 2 + // FOLDER_HOTSEAT = 3 + // FOLDER_WORKSPACE = 4 optional int32 hierarchy = 13; optional bool is_work_profile = 14; // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION optional int32 origin = 15; + + // e.g., number of icons inside a folder + optional int32 cardinality = 16; + + // e.g., (x, y) span of the widget inside homescreen grid system + optional int32 span_x = 17 [default = 1]; + optional int32 span_y = 18 [default = 1]; } /** diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index 3618bb0dd08b..78e6f094db7e 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -86,6 +86,7 @@ bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { // in unit tests. In process calls are not oneway. Status status = mCallback->onPullAtom(mTagId, resultReceiver); if (!status.isOk()) { + StatsdStats::getInstance().notePullBinderCallFailed(mTagId); return false; } diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index 5192ddf301e1..829a60345ba7 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -82,6 +82,11 @@ bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields); } + if (mCachedData.empty()) { + VLOG("Data pulled is empty"); + StatsdStats::getInstance().noteEmptyData(mTagId); + } + (*data) = mCachedData; return mHasGoodData; } diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index cfd5d14b0d3b..1a52eb928777 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -111,12 +111,14 @@ bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey, if (uidProviderIt == mPullUidProviders.end()) { ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId, configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); return false; } sp<PullUidProvider> pullUidProvider = uidProviderIt->second.promote(); if (pullUidProvider == nullptr) { ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId, configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); return false; } uids = pullUidProvider->getPullAtomUids(tagId); @@ -140,6 +142,7 @@ bool StatsPullerManager::PullLocked(int tagId, const vector<int32_t>& uids, return ret; } } + StatsdStats::getInstance().notePullerNotFound(tagId); ALOGW("StatsPullerManager: Unknown tagId %d", tagId); return false; // Return early since we don't know what to pull. } else { @@ -288,10 +291,7 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { for (const auto& pullInfo : needToPull) { vector<shared_ptr<LogEvent>> data; bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey, &data); - if (pullSuccess) { - StatsdStats::getInstance().notePullDelay(pullInfo.first->atomTag, - getElapsedRealtimeNs() - elapsedTimeNs); - } else { + if (!pullSuccess) { VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs); } @@ -354,6 +354,11 @@ void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t a std::lock_guard<std::mutex> _l(mLock); VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); + if (callback == nullptr) { + ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag); + return; + } + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true); int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs; int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 46f5dbda5521..c027fffd20a0 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -472,14 +472,19 @@ void StatsdStats::notePullFailed(int atomId) { mPulledAtomStats[atomId].pullFailed++; } -void StatsdStats::noteStatsCompanionPullFailed(int atomId) { +void StatsdStats::notePullUidProviderNotFound(int atomId) { lock_guard<std::mutex> lock(mLock); - mPulledAtomStats[atomId].statsCompanionPullFailed++; + mPulledAtomStats[atomId].pullUidProviderNotFound++; } -void StatsdStats::noteStatsCompanionPullBinderTransactionFailed(int atomId) { +void StatsdStats::notePullerNotFound(int atomId) { lock_guard<std::mutex> lock(mLock); - mPulledAtomStats[atomId].statsCompanionPullBinderTransactionFailed++; + mPulledAtomStats[atomId].pullerNotFound++; +} + +void StatsdStats::notePullBinderCallFailed(int atomId) { + lock_guard<std::mutex> lock(mLock); + mPulledAtomStats[atomId].binderCallFailCount++; } void StatsdStats::noteEmptyData(int atomId) { @@ -608,6 +613,7 @@ void StatsdStats::resetInternalLocked() { for (auto& pullStats : mPulledAtomStats) { pullStats.second.totalPull = 0; pullStats.second.totalPullFromCache = 0; + pullStats.second.minPullIntervalSec = LONG_MAX; pullStats.second.avgPullTimeNs = 0; pullStats.second.maxPullTimeNs = 0; pullStats.second.numPullTime = 0; @@ -617,9 +623,13 @@ void StatsdStats::resetInternalLocked() { pullStats.second.dataError = 0; pullStats.second.pullTimeout = 0; pullStats.second.pullExceedMaxDelay = 0; + pullStats.second.pullFailed = 0; + pullStats.second.pullUidProviderNotFound = 0; + pullStats.second.pullerNotFound = 0; pullStats.second.registeredCount = 0; pullStats.second.unregisteredCount = 0; pullStats.second.atomErrorCount = 0; + pullStats.second.binderCallFailCount = 0; } mAtomMetricStats.clear(); mActivationBroadcastGuardrailStats.clear(); @@ -764,14 +774,16 @@ void StatsdStats::dumpStats(int out) const { " (average pull time nanos)%lld, (max pull time nanos)%lld, (average pull delay " "nanos)%lld, " " (max pull delay nanos)%lld, (data error)%ld\n" - " (pull timeout)%ld, (pull exceed max delay)%ld\n" - " (registered count) %ld, (unregistered count) %ld\n" + " (pull timeout)%ld, (pull exceed max delay)%ld" + " (no uid provider count)%ld, (no puller found count)%ld\n" + " (registered count) %ld, (unregistered count) %ld" " (atom error count) %d\n", (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, + pair.second.pullUidProviderNotFound, pair.second.pullerNotFound, pair.second.registeredCount, pair.second.unregisteredCount, pair.second.atomErrorCount); } diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 805281ccd2d2..3d0eeb840064 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -371,21 +371,30 @@ public: int32_t lastAtomTag, int32_t uid, int32_t pid); /** - * Records that the pull of an atom has failed + * Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if + * the pull timed out, or if the outgoing binder call failed. + * This count will only increment if the puller was actually invoked. + * + * It does not include a pull not occurring due to not finding the appropriate + * puller. These cases are covered in other counts. */ void notePullFailed(int atomId); /** - * Records that the pull of StatsCompanionService atom has failed + * Records that the pull of an atom has failed due to not having a uid provider. + */ + void notePullUidProviderNotFound(int atomId); + + /** + * Records that the pull of an atom has failed due not finding a puller registered by a + * trusted uid. */ - void noteStatsCompanionPullFailed(int atomId); + void notePullerNotFound(int atomId); /** - * Records that the pull of a StatsCompanionService atom has failed due to a failed binder - * transaction. This can happen when StatsCompanionService returns too - * much data (the max Binder parcel size is 1MB) + * Records that the pull has failed due to the outgoing binder call failing. */ - void noteStatsCompanionPullBinderTransactionFailed(int atomId); + void notePullBinderCallFailed(int atomId); /** * A pull with no data occurred @@ -503,12 +512,13 @@ public: long pullTimeout = 0; long pullExceedMaxDelay = 0; long pullFailed = 0; - long statsCompanionPullFailed = 0; - long statsCompanionPullBinderTransactionFailed = 0; + long pullUidProviderNotFound = 0; + long pullerNotFound = 0; long emptyData = 0; long registeredCount = 0; long unregisteredCount = 0; int32_t atomErrorCount = 0; + long binderCallFailCount = 0; } PulledAtomStats; typedef struct { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index c4bd0549465a..cc4c56537c4c 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -326,13 +326,12 @@ void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { return; } const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); if (pullDelayNs > mMaxPullDelayNs) { ALOGE("Pull finish too late for atom %d", mPullTagId); StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); return; } - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); for (const auto& data : allData) { LogEvent localCopy = data->makeCopy(); localCopy.setElapsedTimestampNs(timestampNs); @@ -415,6 +414,13 @@ void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven if (!pullSuccess || allData.size() == 0) { return; } + const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); + if (pullDelayNs > mMaxPullDelayNs) { + ALOGE("Pull finish too late for atom %d", mPullTagId); + StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); + return; + } for (const auto& data : allData) { if (mEventMatcherWizard->matchLogEvent( *data, mWhatMatcherIndex) == MatchingState::kMatched) { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index f34423a27aff..e5ec72e3d0f5 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -584,11 +584,6 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log return; } - if (allData.size() == 0) { - VLOG("Data pulled is empty"); - StatsdStats::getInstance().noteEmptyData(mPullTagId); - } - mMatchedMetricDimensionKeys.clear(); for (const auto& data : allData) { LogEvent localCopy = data->makeCopy(); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 868247bc9d64..1121392f1db0 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -456,12 +456,15 @@ message StatsdStatsReport { optional int64 pull_timeout = 10; optional int64 pull_exceed_max_delay = 11; optional int64 pull_failed = 12; - optional int64 stats_companion_pull_failed = 13; - optional int64 stats_companion_pull_binder_transaction_failed = 14; + optional int64 stats_companion_pull_failed = 13 [deprecated = true]; + optional int64 stats_companion_pull_binder_transaction_failed = 14 [deprecated = true]; optional int64 empty_data = 15; optional int64 registered_count = 16; optional int64 unregistered_count = 17; optional int32 atom_error_count = 18; + optional int64 binder_call_failed = 19; + optional int64 failed_uid_provider_not_found = 20; + optional int64 puller_not_found = 21; } repeated PulledAtomStats pulled_atom_stats = 10; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 2acffee0f443..bafdfcba59b2 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -74,12 +74,13 @@ const int FIELD_ID_DATA_ERROR = 9; const int FIELD_ID_PULL_TIMEOUT = 10; const int FIELD_ID_PULL_EXCEED_MAX_DELAY = 11; const int FIELD_ID_PULL_FAILED = 12; -const int FIELD_ID_STATS_COMPANION_FAILED = 13; -const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14; const int FIELD_ID_EMPTY_DATA = 15; const int FIELD_ID_PULL_REGISTERED_COUNT = 16; const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; const int FIELD_ID_ATOM_ERROR_COUNT = 18; +const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19; +const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20; +const int FIELD_ID_PULLER_NOT_FOUND = 21; // for AtomMetricStats proto const int FIELD_ID_ATOM_METRIC_STATS = 17; @@ -483,10 +484,6 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> (long long)pair.second.pullExceedMaxDelay); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, (long long)pair.second.pullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_FAILED, - (long long)pair.second.statsCompanionPullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED, - (long long)pair.second.statsCompanionPullBinderTransactionFailed); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, (long long)pair.second.emptyData); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT, @@ -494,6 +491,12 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, (long long) pair.second.unregisteredCount); protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT, + (long long)pair.second.binderCallFailCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND, + (long long)pair.second.pullUidProviderNotFound); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND, + (long long)pair.second.pullerNotFound); protoOutput->end(token); } diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index cdde603f4c0b..948d58706971 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -302,7 +302,10 @@ TEST(StatsdStatsTest, TestPullAtomStats) { stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false); stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); - + stats.notePullBinderCallFailed(util::DISK_SPACE); + stats.notePullUidProviderNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); vector<uint8_t> output; stats.dumpStats(&output, false); @@ -322,6 +325,9 @@ TEST(StatsdStatsTest, TestPullAtomStats) { EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos()); EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count()); EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found()); + EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found()); } TEST(StatsdStatsTest, TestAtomMetricsStats) { diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index fe13b8f26d78..f23681373f53 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -58,7 +58,7 @@ public class DreamManager { @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull ComponentName name) { try { - mService.testDream(mContext.getUserId(), name); + mService.dream(); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -99,4 +99,22 @@ public class DreamManager { e.rethrowFromSystemServer(); } } + + /** + * Returns whether the device is Dreaming. + * + * <p> This is only used for testing the dream service APIs. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) + public boolean isDreaming() { + try { + return mService.isDreaming(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 833bfed573b2..e84c5e574713 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -99,7 +99,6 @@ interface IActivityManager { void unregisterUidObserver(in IUidObserver observer); boolean isUidActive(int uid, String callingPackage); int getUidProcessState(int uid, in String callingPackage); - boolean isUidActiveOrForeground(int uid, String callingPackage); // =============== End of transactions used on native side as well ============================ // Special low-level communication with activity manager. @@ -673,4 +672,9 @@ interface IActivityManager { * @param state The customized state data */ void setProcessStateSummary(in byte[] state); + + /** + * Return whether the app freezer is supported (true) or not (false) by this system. + */ + boolean isAppFreezerSupported(); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0a4627da223a..c409613589b0 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1449,7 +1449,7 @@ public abstract class ContentResolver implements ContentInterface { * on these schemes. * * @param uri The desired URI. - * @return InputStream + * @return InputStream or {@code null} if the provider recently crashed. * @throws FileNotFoundException if the provided URI could not be opened. * @see #openAssetFileDescriptor(Uri, String) */ @@ -1484,6 +1484,9 @@ public abstract class ContentResolver implements ContentInterface { /** * Synonym for {@link #openOutputStream(Uri, String) * openOutputStream(uri, "w")}. + * + * @param uri The desired URI. + * @return an OutputStream or {@code null} if the provider recently crashed. * @throws FileNotFoundException if the provided URI could not be opened. */ public final @Nullable OutputStream openOutputStream(@NonNull Uri uri) @@ -1506,7 +1509,7 @@ public abstract class ContentResolver implements ContentInterface { * * @param uri The desired URI. * @param mode May be "w", "wa", "rw", or "rwt". - * @return OutputStream + * @return an OutputStream or {@code null} if the provider recently crashed. * @throws FileNotFoundException if the provided URI could not be opened. * @see #openAssetFileDescriptor(Uri, String) */ @@ -1563,8 +1566,9 @@ public abstract class ContentResolver implements ContentInterface { * @param uri The desired URI to open. * @param mode The file mode to use, as per {@link ContentProvider#openFile * ContentProvider.openFile}. - * @return Returns a new ParcelFileDescriptor pointing to the file. You - * own this descriptor and are responsible for closing it when done. + * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the + * provider recently crashed. You own this descriptor and are responsible for closing it + * when done. * @throws FileNotFoundException Throws FileNotFoundException if no * file exists under the URI or the mode is invalid. * @see #openAssetFileDescriptor(Uri, String) @@ -1608,8 +1612,9 @@ public abstract class ContentResolver implements ContentInterface { * @param cancellationSignal A signal to cancel the operation in progress, * or null if none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. - * @return Returns a new ParcelFileDescriptor pointing to the file. You - * own this descriptor and are responsible for closing it when done. + * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the + * provider recently crashed. You own this descriptor and are responsible for closing it + * when done. * @throws FileNotFoundException Throws FileNotFoundException if no * file exists under the URI or the mode is invalid. * @see #openAssetFileDescriptor(Uri, String) @@ -1698,8 +1703,9 @@ public abstract class ContentResolver implements ContentInterface { * @param uri The desired URI to open. * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile * ContentProvider.openAssetFile}. - * @return Returns a new ParcelFileDescriptor pointing to the file. You - * own this descriptor and are responsible for closing it when done. + * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the + * provider recently crashed. You own this descriptor and are responsible for closing it + * when done. * @throws FileNotFoundException Throws FileNotFoundException of no * file exists under the URI or the mode is invalid. */ @@ -1754,8 +1760,9 @@ public abstract class ContentResolver implements ContentInterface { * @param cancellationSignal A signal to cancel the operation in progress, or null if * none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. - * @return Returns a new ParcelFileDescriptor pointing to the file. You - * own this descriptor and are responsible for closing it when done. + * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the + * provider recently crashed. You own this descriptor and are responsible for closing it + * when done. * @throws FileNotFoundException Throws FileNotFoundException of no * file exists under the URI or the mode is invalid. */ @@ -1902,9 +1909,9 @@ public abstract class ContentResolver implements ContentInterface { * it is returning. * @param opts Additional provider-dependent options. * @return Returns a new ParcelFileDescriptor from which you can read the - * data stream from the provider. Note that this may be a pipe, meaning - * you can't seek in it. The only seek you should do is if the - * AssetFileDescriptor contains an offset, to move to that offset before + * data stream from the provider or {@code null} if the provider recently crashed. + * Note that this may be a pipe, meaning you can't seek in it. The only seek you + * should do is if the AssetFileDescriptor contains an offset, to move to that offset before * reading. You own this descriptor and are responsible for closing it when done. * @throws FileNotFoundException Throws FileNotFoundException of no * data of the desired type exists under the URI. @@ -1938,9 +1945,9 @@ public abstract class ContentResolver implements ContentInterface { * or null if none. If the operation is canceled, then * {@link OperationCanceledException} will be thrown. * @return Returns a new ParcelFileDescriptor from which you can read the - * data stream from the provider. Note that this may be a pipe, meaning - * you can't seek in it. The only seek you should do is if the - * AssetFileDescriptor contains an offset, to move to that offset before + * data stream from the provider or {@code null} if the provider recently crashed. + * Note that this may be a pipe, meaning you can't seek in it. The only seek you + * should do is if the AssetFileDescriptor contains an offset, to move to that offset before * reading. You own this descriptor and are responsible for closing it when done. * @throws FileNotFoundException Throws FileNotFoundException of no * data of the desired type exists under the URI. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d06a69caf837..4d718ef6bebe 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -47,8 +47,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.PackageParser.PackageParserException; import android.content.pm.dex.ArtManager; +import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Rect; @@ -6037,28 +6040,24 @@ public abstract class PackageManager { @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, @PackageInfoFlags int flags) { - final PackageParser parser = new PackageParser(); - parser.setCallback(new PackageParser.CallbackImpl(this)); - final File apkFile = new File(archiveFilePath); - try { - if ((flags & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) { - // Caller expressed an explicit opinion about what encryption - // aware/unaware components they want to see, so fall through and - // give them what they want - } else { - // Caller expressed no opinion, so match everything - flags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; - } + if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) { + // Caller expressed no opinion about what encryption + // aware/unaware components they want to see, so match both + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + } - PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); - if ((flags & GET_SIGNATURES) != 0) { - PackageParser.collectCertificates(pkg, false /* skipVerify */); - } - PackageUserState state = new PackageUserState(); - return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); - } catch (PackageParserException e) { + boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0 + || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0; + + ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime( + new File(archiveFilePath), 0, collectCertificates); + if (result.isError()) { return null; } + return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null, + new PackageUserState(), UserHandle.getCallingUserId()); } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3b3521f834aa..312e98e77636 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1517,6 +1517,10 @@ public class PackageParser { ? null : "must have at least one '.' separator"; } + /** + * @deprecated Use {@link android.content.pm.parsing.ApkLiteParseUtils#parsePackageSplitNames} + */ + @Deprecated public static Pair<String, String> parsePackageSplitNames(XmlPullParser parser, AttributeSet attrs) throws IOException, XmlPullParserException, PackageParserException { diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 2f416a2538ba..d2172d3741d1 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -16,6 +16,8 @@ package android.content.pm.parsing; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.compat.annotation.UnsupportedAppUsage; @@ -23,6 +25,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.VerifierInfo; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; import android.content.res.ApkAssets; import android.content.res.XmlResourceParser; import android.os.Trace; @@ -70,82 +74,93 @@ public class ApkLiteParseUtils { * * @see PackageParser#parsePackage(File, int) */ - @UnsupportedAppUsage - public static PackageParser.PackageLite parsePackageLite(File packageFile, int flags) - throws PackageParser.PackageParserException { + public static ParseResult<PackageParser.PackageLite> parsePackageLite(ParseInput input, + File packageFile, int flags) { if (packageFile.isDirectory()) { - return parseClusterPackageLite(packageFile, flags); + return parseClusterPackageLite(input, packageFile, flags); } else { - return parseMonolithicPackageLite(packageFile, flags); + return parseMonolithicPackageLite(input, packageFile, flags); } } - public static PackageParser.PackageLite parseMonolithicPackageLite(File packageFile, int flags) - throws PackageParser.PackageParserException { + public static ParseResult<PackageParser.PackageLite> parseMonolithicPackageLite( + ParseInput input, File packageFile, int flags) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); - final PackageParser.ApkLite baseApk = parseApkLite(packageFile, flags); - final String packagePath = packageFile.getAbsolutePath(); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - return new PackageParser.PackageLite(packagePath, baseApk, null, null, null, null, - null, null); + try { + ParseResult<PackageParser.ApkLite> result = parseApkLite(input, packageFile, flags); + if (result.isError()) { + return input.error(result); + } + + final PackageParser.ApkLite baseApk = result.getResult(); + final String packagePath = packageFile.getAbsolutePath(); + return input.success( + new PackageParser.PackageLite(packagePath, baseApk, null, null, null, null, + null, null)); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } } - public static PackageParser.PackageLite parseClusterPackageLite(File packageDir, int flags) - throws PackageParser.PackageParserException { + public static ParseResult<PackageParser.PackageLite> parseClusterPackageLite(ParseInput input, + File packageDir, int flags) { final File[] files = packageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_NOT_APK, "No packages found in split"); + return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, + "No packages found in split"); } // Apk directory is directly nested under the current directory if (files.length == 1 && files[0].isDirectory()) { - return parseClusterPackageLite(files[0], flags); + return parseClusterPackageLite(input, files[0], flags); } String packageName = null; int versionCode = 0; - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>(); - for (File file : files) { - if (PackageParser.isApkFile(file)) { - final PackageParser.ApkLite lite = parseApkLite(file, flags); - - // Assert that all package names and version codes are - // consistent with the first one we encounter. - if (packageName == null) { - packageName = lite.packageName; - versionCode = lite.versionCode; - } else { - if (!packageName.equals(lite.packageName)) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Inconsistent package " + lite.packageName + " in " + file - + "; expected " + packageName); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); + try { + for (File file : files) { + if (PackageParser.isApkFile(file)) { + ParseResult<PackageParser.ApkLite> result = parseApkLite(input, file, flags); + if (result.isError()) { + return input.error(result); } - if (versionCode != lite.versionCode) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Inconsistent version " + lite.versionCode + " in " + file - + "; expected " + versionCode); + + final PackageParser.ApkLite lite = result.getResult(); + // Assert that all package names and version codes are + // consistent with the first one we encounter. + if (packageName == null) { + packageName = lite.packageName; + versionCode = lite.versionCode; + } else { + if (!packageName.equals(lite.packageName)) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent package " + lite.packageName + " in " + file + + "; expected " + packageName); + } + if (versionCode != lite.versionCode) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent version " + lite.versionCode + " in " + file + + "; expected " + versionCode); + } } - } - // Assert that each split is defined only oncuses-static-libe - if (apks.put(lite.splitName, lite) != null) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Split name " + lite.splitName - + " defined more than once; most recent was " + file); + // Assert that each split is defined only oncuses-static-libe + if (apks.put(lite.splitName, lite) != null) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Split name " + lite.splitName + + " defined more than once; most recent was " + file); + } } } + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); final PackageParser.ApkLite baseApk = apks.remove(null); if (baseApk == null) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, + return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, "Missing base APK in " + packageDir); } @@ -180,8 +195,9 @@ public class ApkLiteParseUtils { } final String codePath = packageDir.getAbsolutePath(); - return new PackageParser.PackageLite(codePath, baseApk, splitNames, isFeatureSplits, - usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes); + return input.success(new PackageParser.PackageLite(codePath, baseApk, splitNames, + isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths, + splitRevisionCodes)); } /** @@ -192,9 +208,9 @@ public class ApkLiteParseUtils { * @param flags optional parse flags, such as * {@link PackageParser#PARSE_COLLECT_CERTIFICATES} */ - public static PackageParser.ApkLite parseApkLite(File apkFile, int flags) - throws PackageParser.PackageParserException { - return parseApkLiteInner(apkFile, null, null, flags); + public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, File apkFile, + int flags) { + return parseApkLiteInner(input, apkFile, null, null, flags); } /** @@ -206,13 +222,13 @@ public class ApkLiteParseUtils { * @param flags optional parse flags, such as * {@link PackageParser#PARSE_COLLECT_CERTIFICATES} */ - public static PackageParser.ApkLite parseApkLite(FileDescriptor fd, String debugPathName, - int flags) throws PackageParser.PackageParserException { - return parseApkLiteInner(null, fd, debugPathName, flags); + public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, + FileDescriptor fd, String debugPathName, int flags) { + return parseApkLiteInner(input, null, fd, debugPathName, flags); } - private static PackageParser.ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, - String debugPathName, int flags) throws PackageParser.PackageParserException { + private static ParseResult<PackageParser.ApkLite> parseApkLiteInner(ParseInput input, + File apkFile, FileDescriptor fd, String debugPathName, int flags) { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); XmlResourceParser parser = null; @@ -223,8 +239,7 @@ public class ApkLiteParseUtils { ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */) : ApkAssets.loadFromPath(apkPath); } catch (IOException e) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_NOT_APK, + return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath, e); } @@ -235,9 +250,15 @@ public class ApkLiteParseUtils { final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - signingDetails = ParsingPackageUtils.collectCertificates(apkFile.getAbsolutePath(), - skipVerify, false, PackageParser.SigningDetails.UNKNOWN, - DEFAULT_TARGET_SDK_VERSION); + ParseResult<PackageParser.SigningDetails> result = + ParsingPackageUtils.getSigningDetails(input, + apkFile.getAbsolutePath(), skipVerify, false, + PackageParser.SigningDetails.UNKNOWN, + DEFAULT_TARGET_SDK_VERSION); + if (result.isError()) { + return input.error(result); + } + signingDetails = result.getResult(); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -246,12 +267,10 @@ public class ApkLiteParseUtils { } final AttributeSet attrs = parser; - return parseApkLite(apkPath, parser, attrs, signingDetails); - + return parseApkLite(input, apkPath, parser, attrs, signingDetails); } catch (XmlPullParserException | IOException | RuntimeException e) { Slog.w(TAG, "Failed to parse " + apkPath, e); - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); @@ -265,12 +284,16 @@ public class ApkLiteParseUtils { } } - private static PackageParser.ApkLite parseApkLite( + private static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, String codePath, XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails) - throws IOException, XmlPullParserException, PackageParser.PackageParserException { - final Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames( - parser, attrs); + throws IOException, XmlPullParserException { + ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs); + if (result.isError()) { + return input.error(result); + } + + Pair<String, String> packageSplit = result.getResult(); int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; @@ -394,8 +417,7 @@ public class ApkLiteParseUtils { usesSplitName = attrs.getAttributeValue(PackageParser.ANDROID_RESOURCES, "name"); if (usesSplitName == null) { - throw new PackageParser.PackageParserException( - PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } } else if (PackageParser.TAG_USES_SDK.equals(parser.getName())) { @@ -423,12 +445,54 @@ public class ApkLiteParseUtils { overlayPriority = 0; } - return new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second, - isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, - versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, - coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, - isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, - targetSdkVersion); + return input.success(new PackageParser.ApkLite(codePath, packageSplit.first, + packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, + versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, + signingDetails, coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, + extractNativeLibs, isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, + minSdkVersion, targetSdkVersion)); + } + + public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input, + XmlPullParser parser, AttributeSet attrs) throws IOException, XmlPullParserException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + + if (type != XmlPullParser.START_TAG) { + return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "No start tag found"); + } + if (!parser.getName().equals(PackageParser.TAG_MANIFEST)) { + return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "No <manifest> tag"); + } + + final String packageName = attrs.getAttributeValue(null, "package"); + if (!"android".equals(packageName)) { + final String error = PackageParser.validateName(packageName, true, true); + if (error != null) { + return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, + "Invalid manifest package: " + error); + } + } + + String splitName = attrs.getAttributeValue(null, "split"); + if (splitName != null) { + if (splitName.length() == 0) { + splitName = null; + } else { + final String error = PackageParser.validateName(splitName, false, false); + if (error != null) { + return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, + "Invalid manifest split: " + error); + } + } + } + + return input.success(Pair.create(packageName.intern(), + (splitName != null) ? splitName.intern() : splitName)); } public static VerifierInfo parseVerifier(AttributeSet attrs) { diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index d3d15c82dd1b..9372c957af34 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -29,6 +29,7 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.annotation.AnyRes; +import android.annotation.CheckResult; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -135,23 +136,32 @@ public class ParsingPackageUtils { * for feature support. */ @NonNull - public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, int flags, - @NonNull ParseInput.Callback inputCallback, @NonNull Callback callback) { - if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) { - // Caller expressed no opinion about what encryption - // aware/unaware components they want to see, so match both - flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - } - - ParseInput input = new ParseTypeImpl(inputCallback).reset(); + public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, + @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) { + ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); ParseResult<ParsingPackage> result; - - ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, callback); + ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() { + @Override + public boolean hasFeature(String feature) { + // Assume the device doesn't support anything. This will affect permission parsing + // and will force <uses-permission/> declarations to include all requiredNotFeature + // permissions and exclude all requiredFeature permissions. This mirrors the old + // behavior. + return false; + } + + @Override + public ParsingPackage startParsingPackage( + @NonNull String packageName, + @NonNull String baseCodePath, + @NonNull String codePath, + @NonNull TypedArray manifestArray, boolean isCoreApp) { + return new ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray); + } + }); try { - result = parser.parsePackage(input, file, flags); + result = parser.parsePackage(input, file, parseFlags); if (result.isError()) { return result; } @@ -162,9 +172,9 @@ public class ParsingPackageUtils { try { ParsingPackage pkg = result.getResult(); - if ((flags & PackageManager.GET_SIGNATURES) != 0 - || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { - ParsingPackageUtils.collectCertificates(pkg, false /* skipVerify */); + if (collectCertificates) { + pkg.setSigningDetails( + ParsingPackageUtils.getSigningDetails(pkg, false /* skipVerify */)); } return input.success(pkg); @@ -197,7 +207,7 @@ public class ParsingPackageUtils { * and unique split names. * <p> * Note that this <em>does not</em> perform signature verification; that - * must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}. + * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}. * * If {@code useCaches} is true, the package parser might return a cached * result from a previous parse of the same {@code packageFile} with the same @@ -223,12 +233,17 @@ public class ParsingPackageUtils { * split names. * <p> * Note that this <em>does not</em> perform signature verification; that - * must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}. + * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, - int flags) throws PackageParserException { - final PackageParser.PackageLite lite = ApkLiteParseUtils.parseClusterPackageLite(packageDir, - 0); + int flags) { + ParseResult<PackageParser.PackageLite> liteResult = + ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0); + if (liteResult.isError()) { + return input.error(liteResult); + } + + final PackageParser.PackageLite lite = liteResult.getResult(); if (mOnlyCoreApps && !lite.coreApp) { return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, "Not a coreApp: " + packageDir); @@ -275,6 +290,9 @@ public class ParsingPackageUtils { pkg.setUse32BitAbi(lite.use32bitAbi); return input.success(pkg); + } catch (PackageParserException e) { + return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to load assets: " + lite.baseCodePath, e); } finally { IoUtils.closeQuietly(assetLoader); } @@ -284,12 +302,17 @@ public class ParsingPackageUtils { * Parse the given APK file, treating it as as a single monolithic package. * <p> * Note that this <em>does not</em> perform signature verification; that - * must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}. + * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile, int flags) throws PackageParserException { - final PackageParser.PackageLite lite = ApkLiteParseUtils.parseMonolithicPackageLite(apkFile, - flags); + ParseResult<PackageParser.PackageLite> liteResult = + ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags); + if (liteResult.isError()) { + return input.error(liteResult); + } + + final PackageParser.PackageLite lite = liteResult.getResult(); if (mOnlyCoreApps && !lite.coreApp) { return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, "Not a coreApp: " + apkFile); @@ -430,20 +453,21 @@ public class ParsingPackageUtils { final String splitName; final String pkgName; - try { - Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames(parser, - parser); - pkgName = packageSplit.first; - splitName = packageSplit.second; + ParseResult<Pair<String, String>> packageSplitResult = + ApkLiteParseUtils.parsePackageSplitNames(input, parser, parser); + if (packageSplitResult.isError()) { + return input.error(packageSplitResult); + } - if (!TextUtils.isEmpty(splitName)) { - return input.error( - PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, - "Expected base APK, but found split " + splitName - ); - } - } catch (PackageParserException e) { - return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME); + Pair<String, String> packageSplit = packageSplitResult.getResult(); + pkgName = packageSplit.first; + splitName = packageSplit.second; + + if (!TextUtils.isEmpty(splitName)) { + return input.error( + PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, + "Expected base APK, but found split " + splitName + ); } final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest); @@ -2624,31 +2648,53 @@ public class ParsingPackageUtils { /** * Collect certificates from all the APKs described in the given package. Also asserts that * all APK contents are signed correctly and consistently. + * + * TODO(b/155513789): Remove this in favor of collecting certificates during the original parse + * call if requested. Leaving this as an optional method for the caller means we have to + * construct a dummy ParseInput. */ - public static SigningDetails collectCertificates(ParsingPackageRead pkg, boolean skipVerify) + @CheckResult + public static SigningDetails getSigningDetails(ParsingPackageRead pkg, boolean skipVerify) throws PackageParserException { SigningDetails signingDetails = SigningDetails.UNKNOWN; + ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - signingDetails = collectCertificates( + ParseResult<SigningDetails> result = getSigningDetails( + input, pkg.getBaseCodePath(), skipVerify, pkg.isStaticSharedLibrary(), signingDetails, pkg.getTargetSdkVersion() ); + if (result.isError()) { + throw new PackageParser.PackageParserException(result.getErrorCode(), + result.getErrorMessage(), result.getException()); + } + + signingDetails = result.getResult(); String[] splitCodePaths = pkg.getSplitCodePaths(); if (!ArrayUtils.isEmpty(splitCodePaths)) { for (int i = 0; i < splitCodePaths.length; i++) { - signingDetails = collectCertificates( + result = getSigningDetails( + input, splitCodePaths[i], skipVerify, pkg.isStaticSharedLibrary(), signingDetails, pkg.getTargetSdkVersion() ); + if (result.isError()) { + throw new PackageParser.PackageParserException(result.getErrorCode(), + result.getErrorMessage(), result.getException()); + } + + + signingDetails = result.getResult(); } } return signingDetails; @@ -2657,9 +2703,10 @@ public class ParsingPackageUtils { } } - public static SigningDetails collectCertificates(String baseCodePath, boolean skipVerify, - boolean isStaticSharedLibrary, @NonNull SigningDetails existingSigningDetails, - int targetSdk) throws PackageParserException { + @CheckResult + public static ParseResult<SigningDetails> getSigningDetails(ParseInput input, + String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary, + @NonNull SigningDetails existingSigningDetails, int targetSdk) { int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( targetSdk); if (isStaticSharedLibrary) { @@ -2667,27 +2714,31 @@ public class ParsingPackageUtils { minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; } SigningDetails verified; - if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying - verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( - baseCodePath, minSignatureScheme); - } else { - verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme); + try { + if (skipVerify) { + // systemDir APKs are already trusted, save time by not verifying + verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( + baseCodePath, minSignatureScheme); + } else { + verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme); + } + } catch (PackageParserException e) { + return input.error(PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed collecting certificates for " + baseCodePath, e); } // Verify that entries are signed consistently with the first pkg // we encountered. Note that for splits, certificates may have // already been populated during an earlier parse of a base APK. if (existingSigningDetails == SigningDetails.UNKNOWN) { - return verified; + return input.success(verified); } else { if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) { - throw new PackageParserException( - INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, baseCodePath + " has mismatched certificates"); } - return existingSigningDetails; + return input.success(existingSigningDetails); } } diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java index 61152061ae10..91e571be3d89 100644 --- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java +++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java @@ -18,12 +18,16 @@ package android.content.pm.parsing.result; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.parsing.ParsingUtils; +import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.CollectionUtils; /** @hide */ @@ -61,6 +65,28 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { private Integer mTargetSdkVersion; /** + * Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use + * with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is + * done outside of {@link com.android.server.pm.PackageManagerService}. + */ + public static ParseTypeImpl forDefaultParsing() { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName; + appInfo.targetSdkVersion = targetSdkVersion; + try { + return platformCompat.isChangeEnabled(changeId, appInfo); + } catch (Exception e) { + // This shouldn't happen, but assume enforcement if it does + Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e); + return true; + } + }); + } + + /** * @param callback if nullable, fallback to manual targetSdk > Q check */ public ParseTypeImpl(@NonNull Callback callback) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 187274a837a0..7912dacac377 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -635,10 +635,11 @@ public class UserManager { /** * Specifies if a user is disallowed from adding new users. This can only be set by device - * owners, profile owners on the primary user or profile owners of organization-owned managed - * profiles on the parent profile. The default value is <code>false</code>. + * owners or profile owners on the primary user. The default value is <code>false</code>. * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can add other users. + * <p> When the device is an organization-owned device provisioned with a managed profile, + * this restriction will be set as a base restriction which cannot be removed by any admin. * * <p>Key for user restrictions. * <p>Type: Boolean diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index ed429dd835c3..06caa03e3cb4 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -56,10 +56,15 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.RemoteStream; import com.android.internal.infra.ServiceConnector; +import com.android.internal.os.TransferPipe; import com.android.internal.util.CollectionUtils; import libcore.util.EmptyArray; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -67,7 +72,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** @@ -476,6 +483,36 @@ public final class PermissionControllerManager { } /** + * Dump permission controller state. + * + * @hide + */ + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) { + CompletableFuture<Throwable> dumpResult = new CompletableFuture<>(); + mRemoteService.postForResult( + service -> TransferPipe.dumpAsync(service.asBinder(), args)) + .whenComplete( + (dump, err) -> { + try (FileOutputStream out = new FileOutputStream(fd)) { + out.write(dump); + } catch (IOException | NullPointerException e) { + Log.e(TAG, "Could for forwards permission controller dump", e); + } + + dumpResult.complete(err); + }); + + try { + Throwable err = dumpResult.get(UNBIND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + if (err != null) { + throw err; + } + } catch (Throwable e) { + Log.e(TAG, "Could not dump permission controller state", e); + } + } + + /** * Gets the runtime permissions for an app. * * @param packageName The package for which to query. diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 82a7d788100d..c6ede32d0864 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -50,9 +50,11 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -494,6 +496,11 @@ public abstract class PermissionControllerService extends Service { "packageName cannot be null"); onOneTimePermissionSessionTimeout(packageName); } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + PermissionControllerService.this.dump(fd, writer, args); + } }; } } diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 2e00c0c9d2a4..327bca268a7b 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -1274,8 +1274,6 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_RESULT, path); } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { - enforceReadPermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); return getDocumentMetadata(documentId); } else { throw new UnsupportedOperationException("Method not supported " + method); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 6c8bd7f352d4..c0d0c21af1ab 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14033,6 +14033,14 @@ public final class Settings { "zram_enabled"; /** + * Whether the app freezer is enabled on this device. + * The value of "enabled" enables the app freezer, "disabled" disables it and + * "device_default" will let the system decide whether to enable the freezer or not + * @hide + */ + public static final String CACHED_APPS_FREEZER_ENABLED = "cached_apps_freezer"; + + /** * Configuration flags for smart replies in notifications. * This is encoded as a key=value list, separated by commas. Ex: * diff --git a/core/java/android/service/autofill/IInlineSuggestionUi.aidl b/core/java/android/service/autofill/IInlineSuggestionUi.aidl new file mode 100644 index 000000000000..7289853064f8 --- /dev/null +++ b/core/java/android/service/autofill/IInlineSuggestionUi.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.service.autofill.ISurfacePackageResultCallback; + +/** + * Interface to interact with a remote inline suggestion UI. + * + * @hide + */ +oneway interface IInlineSuggestionUi { + void getSurfacePackage(ISurfacePackageResultCallback callback); + void releaseSurfaceControlViewHost(); +} diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl index 172cfef9fee2..97eb790b9acc 100644 --- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl +++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl @@ -18,17 +18,19 @@ package android.service.autofill; import android.content.IntentSender; import android.os.IBinder; +import android.service.autofill.IInlineSuggestionUi; import android.view.SurfaceControlViewHost; /** - * Interface to receive events from inline suggestions. + * Interface to receive events from a remote inline suggestion UI. * * @hide */ oneway interface IInlineSuggestionUiCallback { void onClick(); void onLongClick(); - void onContent(in SurfaceControlViewHost.SurfacePackage surface, int width, int height); + void onContent(in IInlineSuggestionUi content, in SurfaceControlViewHost.SurfacePackage surface, + int width, int height); void onError(); void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId); void onStartIntentSender(in IntentSender intentSender); diff --git a/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl new file mode 100644 index 000000000000..0c2c624952eb --- /dev/null +++ b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.view.SurfaceControlViewHost; + +/** + * Interface to receive a SurfaceControlViewHost.SurfacePackage. + * + * @hide + */ +oneway interface ISurfacePackageResultCallback { + void onResult(in SurfaceControlViewHost.SurfacePackage result); +} diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java index 6c22b1936d74..3ea443bab3f8 100644 --- a/core/java/android/service/autofill/InlineSuggestionRenderService.java +++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java @@ -33,6 +33,7 @@ import android.os.Looper; import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Log; +import android.util.LruCache; import android.util.Size; import android.view.Display; import android.view.SurfaceControlViewHost; @@ -40,6 +41,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import java.lang.ref.WeakReference; + /** * A service that renders an inline presentation view given the {@link InlinePresentation}. * @@ -65,6 +68,27 @@ public abstract class InlineSuggestionRenderService extends Service { private IInlineSuggestionUiCallback mCallback; + + /** + * A local LRU cache keeping references to the inflated {@link SurfaceControlViewHost}s, so + * they can be released properly when no longer used. Each view needs to be tracked separately, + * therefore for simplicity we use the hash code of the value object as key in the cache. + */ + private final LruCache<InlineSuggestionUiImpl, Boolean> mActiveInlineSuggestions = + new LruCache<InlineSuggestionUiImpl, Boolean>(30) { + @Override + public void entryRemoved(boolean evicted, InlineSuggestionUiImpl key, + Boolean oldValue, + Boolean newValue) { + if (evicted) { + Log.w(TAG, + "Hit max=100 entries in the cache. Releasing oldest one to make " + + "space."); + key.releaseSurfaceControlViewHost(); + } + } + }; + /** * If the specified {@code width}/{@code height} is an exact value, then it will be returned as * is, otherwise the method tries to measure a size that is just large enough to fit the view @@ -169,8 +193,14 @@ public abstract class InlineSuggestionRenderService extends Service { return true; }); - sendResult(callback, host.getSurfacePackage(), measuredSize.getWidth(), - measuredSize.getHeight()); + try { + InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mHandler); + mActiveInlineSuggestions.put(uiImpl, true); + callback.onContent(new InlineSuggestionUiWrapper(uiImpl), host.getSurfacePackage(), + measuredSize.getWidth(), measuredSize.getHeight()); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling onContent()"); + } } finally { updateDisplay(Display.DEFAULT_DISPLAY); } @@ -181,12 +211,87 @@ public abstract class InlineSuggestionRenderService extends Service { callback.sendResult(rendererInfo); } - private void sendResult(@NonNull IInlineSuggestionUiCallback callback, - @Nullable SurfaceControlViewHost.SurfacePackage surface, int width, int height) { - try { - callback.onContent(surface, width, height); - } catch (RemoteException e) { - Log.w(TAG, "RemoteException calling onContent(" + surface + ")"); + /** + * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly + * reference by the remote system server process. + */ + private static final class InlineSuggestionUiWrapper extends + android.service.autofill.IInlineSuggestionUi.Stub { + + private final WeakReference<InlineSuggestionUiImpl> mUiImpl; + + InlineSuggestionUiWrapper(InlineSuggestionUiImpl uiImpl) { + mUiImpl = new WeakReference<>(uiImpl); + } + + @Override + public void releaseSurfaceControlViewHost() { + final InlineSuggestionUiImpl uiImpl = mUiImpl.get(); + if (uiImpl != null) { + uiImpl.releaseSurfaceControlViewHost(); + } + } + + @Override + public void getSurfacePackage(ISurfacePackageResultCallback callback) { + final InlineSuggestionUiImpl uiImpl = mUiImpl.get(); + if (uiImpl != null) { + uiImpl.getSurfacePackage(callback); + } + } + } + + /** + * Keeps track of a SurfaceControlViewHost to ensure it's released when its lifecycle ends. + * + * <p>This class is thread safe, because all the outside calls are piped into a single + * handler thread to be processed. + */ + private final class InlineSuggestionUiImpl { + + @Nullable + private SurfaceControlViewHost mViewHost; + @NonNull + private final Handler mHandler; + + InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) { + this.mViewHost = viewHost; + this.mHandler = handler; + } + + /** + * Call {@link SurfaceControlViewHost#release()} to release it. After this, this view is + * not usable, and any further calls to the + * {@link #getSurfacePackage(ISurfacePackageResultCallback)} will get {@code null} result. + */ + public void releaseSurfaceControlViewHost() { + mHandler.post(() -> { + if (mViewHost == null) { + return; + } + Log.v(TAG, "Releasing inline suggestion view host"); + mViewHost.release(); + mViewHost = null; + InlineSuggestionRenderService.this.mActiveInlineSuggestions.remove( + InlineSuggestionUiImpl.this); + Log.v(TAG, "Removed the inline suggestion from the cache, current size=" + + InlineSuggestionRenderService.this.mActiveInlineSuggestions.size()); + }); + } + + /** + * Sends back a new {@link android.view.SurfaceControlViewHost.SurfacePackage} if the view + * is not released, {@code null} otherwise. + */ + public void getSurfacePackage(ISurfacePackageResultCallback callback) { + Log.d(TAG, "getSurfacePackage"); + mHandler.post(() -> { + try { + callback.onResult(mViewHost == null ? null : mViewHost.getSurfacePackage()); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling onSurfacePackage"); + } + }); } } diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java index 0a29edc81f2f..09d1bb9d2b12 100644 --- a/core/java/android/service/dreams/DreamActivity.java +++ b/core/java/android/service/dreams/DreamActivity.java @@ -21,6 +21,8 @@ import android.app.Activity; import android.os.Bundle; import android.view.WindowInsets; +import com.android.internal.R; + /** * The Activity used by the {@link DreamService} to draw screensaver content * on the screen. This activity runs in dream application's process, but is started by a @@ -56,8 +58,20 @@ public class DreamActivity extends Activity { if (callback != null) { callback.onActivityCreated(this); } + } + @Override + public void onResume() { + super.onResume(); // Hide all insets (nav bar, status bar, etc) when the dream is showing getWindow().getInsetsController().hide(WindowInsets.Type.systemBars()); + overridePendingTransition(R.anim.dream_activity_open_enter, + R.anim.dream_activity_open_exit); + } + + @Override + public void finishAndRemoveTask() { + super.finishAndRemoveTask(); + overridePendingTransition(0, R.anim.dream_activity_close_exit); } } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 337027ef5bc9..d2dfb29ba25c 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1052,7 +1052,6 @@ public class DreamService extends Service implements Window.Callback { mWindow.requestFeature(Window.FEATURE_NO_TITLE); WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.windowAnimations = com.android.internal.R.style.Animation_Dream; lp.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED @@ -1076,8 +1075,12 @@ public class DreamService extends Service implements Window.Callback { @Override public void onViewDetachedFromWindow(View v) { - mActivity = null; - finish(); + if (mActivity == null || !mActivity.isChangingConfigurations()) { + // Only stop the dream if the view is not detached by relaunching + // activity for configuration changes. + mActivity = null; + finish(); + } } }); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index a135b0ca148b..fcc4a6ec4d92 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1117,7 +1117,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * Cancel on-going animation to show/hide {@link InsetsType}. */ @VisibleForTesting - public void cancelExistingAnimation() { + public void cancelExistingAnimations() { cancelExistingControllers(all()); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 58ec9ec11e56..2dcfd899adf4 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -200,15 +200,6 @@ public class InsetsSourceConsumer { } boolean applyLocalVisibilityOverride() { - return applyLocalVisibilityOverride(false /* notifyWithoutControl */); - } - - /** - * @param notifyWithoutControl set it true when the caller wants to notify the visibility - * changes even if the consumer doesn't have the control. - * @return true if it needs to notify the visibility changes to the controller - */ - private boolean applyLocalVisibilityOverride(boolean notifyWithoutControl) { InsetsSource source = mState.peekSource(mType); final boolean isVisible = source != null && source.isVisible(); final boolean hasControl = mSourceControl != null; @@ -220,7 +211,7 @@ public class InsetsSourceConsumer { // If we don't have control, we are not able to change the visibility. if (!hasControl) { - return notifyWithoutControl; + return false; } if (isVisible == mRequestedVisible) { return false; @@ -302,9 +293,7 @@ public class InsetsSourceConsumer { mRequestedVisible = requestedVisible; mIsAnimationPending = false; } - // We need to notify the visibility changed even if we don't have mSourceControl in order - // to update color views. - if (applyLocalVisibilityOverride(true /* notifyWithoutControl */)) { + if (applyLocalVisibilityOverride()) { mController.notifyVisibilityChanged(); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 42f11c162473..a17af6c90617 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4624,6 +4624,8 @@ public final class ViewRootImpl implements ViewParent, setAccessibilityFocus(null, null); + mInsetsController.cancelExistingAnimations(); + mView.assignParent(null); mView = null; mAttachInfo.mRootView = null; diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index d8bf58f78339..9674a80c8159 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -104,10 +104,10 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { @Override public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + if (mViewRoot.mView == null) { + throw new IllegalStateException("View of the ViewRootImpl is not initiated."); + } if (mApplier == null) { - if (mViewRoot.mView == null) { - throw new IllegalStateException("View of the ViewRootImpl is not initiated."); - } mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView); } if (mViewRoot.mView.isHardwareAccelerated()) { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 95d6d651cc79..15604a2ae2c1 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -41,6 +41,7 @@ public class WindowlessWindowManager implements IWindowSession { private final static String TAG = "WindowlessWindowManager"; private class State { + //TODO : b/150190730 we should create it when view show and release it when view invisible. SurfaceControl mSurfaceControl; WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); int mDisplayId; @@ -239,21 +240,19 @@ public class WindowlessWindowManager implements IWindowSession { } WindowManager.LayoutParams attrs = state.mParams; - final Rect surfaceInsets = attrs.surfaceInsets; - int width = surfaceInsets != null ? - attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width; - int height = surfaceInsets != null ? - attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height; - - t.setBufferSize(sc, width, height) - .setOpaque(sc, isOpaque(attrs)); if (viewFlags == View.VISIBLE) { - t.show(sc); + final Rect surfaceInsets = attrs.surfaceInsets; + int width = surfaceInsets != null + ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width; + int height = surfaceInsets != null + ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height; + + t.setBufferSize(sc, width, height).setOpaque(sc, isOpaque(attrs)).show(sc).apply(); + outSurfaceControl.copyFrom(sc); } else { - t.hide(sc); + t.hide(sc).apply(); + outSurfaceControl.release(); } - t.apply(); - outSurfaceControl.copyFrom(sc); outFrame.set(0, 0, attrs.width, attrs.height); mergedConfiguration.setConfiguration(mConfiguration, mConfiguration); diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index 6b1a480986c8..4c72474435a4 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -18,11 +18,13 @@ package android.view.inputmethod; import android.annotation.BinderThread; import android.annotation.CallbackExecutor; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.Context; -import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -42,26 +44,26 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * This class represents an inline suggestion which is made by one app - * and can be embedded into the UI of another. Suggestions may contain - * sensitive information not known to the host app which needs to be - * protected from spoofing. To address that the suggestion view inflated - * on demand for embedding is created in such a way that the hosting app - * cannot introspect its content and cannot interact with it. + * This class represents an inline suggestion which is made by one app and can be embedded into the + * UI of another. Suggestions may contain sensitive information not known to the host app which + * needs to be protected from spoofing. To address that the suggestion view inflated on demand for + * embedding is created in such a way that the hosting app cannot introspect its content and cannot + * interact with it. */ -@DataClass( - genEqualsHashCode = true, - genToString = true, - genHiddenConstDefs = true, +@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstDefs = true, genHiddenConstructor = true) -@DataClass.Suppress({"getContentProvider"}) public final class InlineSuggestion implements Parcelable { private static final String TAG = "InlineSuggestion"; - private final @NonNull InlineSuggestionInfo mInfo; + @NonNull + private final InlineSuggestionInfo mInfo; - private final @Nullable IInlineContentProvider mContentProvider; + /** + * @hide + */ + @Nullable + private final IInlineContentProvider mContentProvider; /** * Used to keep a strong reference to the callback so it doesn't get garbage collected. @@ -69,7 +71,8 @@ public final class InlineSuggestion implements Parcelable { * @hide */ @DataClass.ParcelWith(InlineContentCallbackImplParceling.class) - private @Nullable InlineContentCallbackImpl mInlineContentCallback; + @Nullable + private InlineContentCallbackImpl mInlineContentCallback; /** * Creates a new {@link InlineSuggestion}, for testing purpose. @@ -87,8 +90,7 @@ public final class InlineSuggestion implements Parcelable { * * @hide */ - public InlineSuggestion( - @NonNull InlineSuggestionInfo info, + public InlineSuggestion(@NonNull InlineSuggestionInfo info, @Nullable IInlineContentProvider contentProvider) { this(info, contentProvider, /* inlineContentCallback */ null); } @@ -96,25 +98,30 @@ public final class InlineSuggestion implements Parcelable { /** * Inflates a view with the content of this suggestion at a specific size. * - * <p> The size must be either 1) between the - * {@link android.widget.inline.InlinePresentationSpec#getMinSize() min size} and the - * {@link android.widget.inline.InlinePresentationSpec#getMaxSize() max size} of the - * presentation spec returned by {@link InlineSuggestionInfo#getInlinePresentationSpec()}, - * or 2) {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If the size is set to - * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just - * large enough to fit the content, while still conforming to the min / max size specified by - * the {@link android.widget.inline.InlinePresentationSpec}. + * <p> Each dimension of the size must satisfy one of the following conditions: + * + * <ol> + * <li>between {@link android.widget.inline.InlinePresentationSpec#getMinSize()} and + * {@link android.widget.inline.InlinePresentationSpec#getMaxSize()} of the presentation spec + * from {@code mInfo} + * <li>{@link ViewGroup.LayoutParams#WRAP_CONTENT} + * </ol> + * + * If the size is set to {@link + * ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just large + * enough to fit the content, while still conforming to the min / max size specified by the + * {@link android.widget.inline.InlinePresentationSpec}. * * <p> The caller can attach an {@link android.view.View.OnClickListener} and/or an - * {@link android.view.View.OnLongClickListener} to the view in the - * {@code callback} to receive click and long click events on the view. + * {@link android.view.View.OnLongClickListener} to the view in the {@code callback} to receive + * click and long click events on the view. * * @param context Context in which to inflate the view. - * @param size The size at which to inflate the suggestion. For each dimension, it maybe - * an exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}. - * @param callback Callback for receiving the inflated view, where the - * {@link ViewGroup.LayoutParams} of the view is set as the actual size of - * the underlying remote view. + * @param size The size at which to inflate the suggestion. For each dimension, it maybe an + * exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}. + * @param callback Callback for receiving the inflated view, where the {@link + * ViewGroup.LayoutParams} of the view is set as the actual size of the + * underlying remote view. * @throws IllegalArgumentException If an invalid argument is passed. * @throws IllegalStateException If this method is already called. */ @@ -130,19 +137,17 @@ public final class InlineSuggestion implements Parcelable { + ", nor wrap_content"); } mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback); - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - if (mContentProvider == null) { - callback.accept(/* view */ null); - return; - } - try { - mContentProvider.provideContent(size.getWidth(), size.getHeight(), - new InlineContentCallbackWrapper(mInlineContentCallback)); - } catch (RemoteException e) { - Slog.w(TAG, "Error creating suggestion content surface: " + e); - callback.accept(/* view */ null); - } - }); + if (mContentProvider == null) { + callbackExecutor.execute(() -> callback.accept(/* view */ null)); + return; + } + try { + mContentProvider.provideContent(size.getWidth(), size.getHeight(), + new InlineContentCallbackWrapper(mInlineContentCallback)); + } catch (RemoteException e) { + Slog.w(TAG, "Error creating suggestion content surface: " + e); + callbackExecutor.execute(() -> callback.accept(/* view */ null)); + } } /** @@ -161,9 +166,14 @@ public final class InlineSuggestion implements Parcelable { if (mInlineContentCallback != null) { throw new IllegalStateException("Already called #inflate()"); } - return new InlineContentCallbackImpl(context, callbackExecutor, callback); + return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor, + callback); } + /** + * A wrapper class around the {@link InlineContentCallbackImpl} to ensure it's not strongly + * reference by the remote system server process. + */ private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub { private final WeakReference<InlineContentCallbackImpl> mCallbackImpl; @@ -201,17 +211,68 @@ public final class InlineSuggestion implements Parcelable { } } + /** + * Handles the communication between the inline suggestion view in current (IME) process and + * the remote view provided from the system server. + * + * <p>This class is thread safe, because all the outside calls are piped into a single + * handler thread to be processed. + */ private static final class InlineContentCallbackImpl { - private final @NonNull Context mContext; - private final @NonNull Executor mCallbackExecutor; - private final @NonNull Consumer<InlineContentView> mCallback; - private @Nullable InlineContentView mView; + @NonNull + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + @NonNull + private final Context mContext; + @Nullable + private final IInlineContentProvider mInlineContentProvider; + @NonNull + private final Executor mCallbackExecutor; + + /** + * Callback from the client (IME) that will receive the inflated suggestion view. It'll + * only be called once when the view SurfacePackage is first sent back to the client. Any + * updates to the view due to attach to window and detach from window events will be + * handled under the hood, transparent from the client. + */ + @NonNull + private final Consumer<InlineContentView> mCallback; + + /** + * Indicates whether the first content has been received or not. + */ + private boolean mFirstContentReceived = false; + + /** + * The client (IME) side view which internally wraps a remote view. It'll be set when + * {@link #onContent(SurfaceControlViewHost.SurfacePackage, int, int)} is called, which + * should only happen once in the lifecycle of this inline suggestion instance. + */ + @Nullable + private InlineContentView mView; + + /** + * The SurfacePackage pointing to the remote view. It's cached here to be sent to the next + * available consumer. + */ + @Nullable + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + + /** + * The callback (from the {@link InlineContentView}) which consumes the surface package. + * It's cached here to be called when the SurfacePackage is returned from the remote + * view owning process. + */ + @Nullable + private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer; InlineContentCallbackImpl(@NonNull Context context, + @Nullable IInlineContentProvider inlineContentProvider, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer<InlineContentView> callback) { mContext = context; + mInlineContentProvider = inlineContentProvider; mCallbackExecutor = callbackExecutor; mCallback = callback; } @@ -219,28 +280,110 @@ public final class InlineSuggestion implements Parcelable { @BinderThread public void onContent(SurfaceControlViewHost.SurfacePackage content, int width, int height) { - if (content == null) { + mMainHandler.post(() -> handleOnContent(content, width, height)); + } + + @MainThread + private void handleOnContent(SurfaceControlViewHost.SurfacePackage content, int width, + int height) { + if (!mFirstContentReceived) { + handleOnFirstContentReceived(content, width, height); + mFirstContentReceived = true; + } else { + handleOnSurfacePackage(content); + } + } + + /** + * Called when the view content is returned for the first time. + */ + @MainThread + private void handleOnFirstContentReceived(SurfaceControlViewHost.SurfacePackage content, + int width, int height) { + mSurfacePackage = content; + if (mSurfacePackage == null) { mCallbackExecutor.execute(() -> mCallback.accept(/* view */null)); } else { mView = new InlineContentView(mContext); mView.setLayoutParams(new ViewGroup.LayoutParams(width, height)); - mView.setChildSurfacePackage(content); + mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater()); mCallbackExecutor.execute(() -> mCallback.accept(mView)); } } + /** + * Called when any subsequent SurfacePackage is returned from the remote view owning + * process. + */ + @MainThread + private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) { + mSurfacePackage = surfacePackage; + if (mSurfacePackage != null && mSurfacePackageConsumer != null) { + mSurfacePackageConsumer.accept(mSurfacePackage); + mSurfacePackageConsumer = null; + } + } + + @MainThread + private void handleOnSurfacePackageReleased() { + mSurfacePackage = null; + try { + mInlineContentProvider.onSurfacePackageReleased(); + } catch (RemoteException e) { + Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e); + } + } + + @MainThread + private void handleGetSurfacePackage( + Consumer<SurfaceControlViewHost.SurfacePackage> consumer) { + if (mSurfacePackage != null) { + consumer.accept(mSurfacePackage); + } else { + mSurfacePackageConsumer = consumer; + try { + mInlineContentProvider.requestSurfacePackage(); + } catch (RemoteException e) { + Slog.w(TAG, "Error calling getSurfacePackage(): " + e); + consumer.accept(null); + mSurfacePackageConsumer = null; + } + } + } + + private InlineContentView.SurfacePackageUpdater getSurfacePackageUpdater() { + return new InlineContentView.SurfacePackageUpdater() { + @Override + public void onSurfacePackageReleased() { + mMainHandler.post( + () -> InlineContentCallbackImpl.this.handleOnSurfacePackageReleased()); + } + + @Override + public void getSurfacePackage( + Consumer<SurfaceControlViewHost.SurfacePackage> consumer) { + mMainHandler.post( + () -> InlineContentCallbackImpl.this.handleGetSurfacePackage(consumer)); + } + }; + } + @BinderThread public void onClick() { - if (mView != null && mView.hasOnClickListeners()) { - mView.callOnClick(); - } + mMainHandler.post(() -> { + if (mView != null && mView.hasOnClickListeners()) { + mView.callOnClick(); + } + }); } @BinderThread public void onLongClick() { - if (mView != null && mView.hasOnLongClickListeners()) { - mView.performLongClick(); - } + mMainHandler.post(() -> { + if (mView != null && mView.hasOnLongClickListeners()) { + mView.performLongClick(); + } + }); } } @@ -262,6 +405,7 @@ public final class InlineSuggestion implements Parcelable { + // Code below generated by codegen v1.0.15. // // DO NOT MODIFY! @@ -302,6 +446,14 @@ public final class InlineSuggestion implements Parcelable { } /** + * @hide + */ + @DataClass.Generated.Member + public @Nullable IInlineContentProvider getContentProvider() { + return mContentProvider; + } + + /** * Used to keep a strong reference to the callback so it doesn't get garbage collected. * * @hide @@ -421,7 +573,7 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1587771173367L, + time = 1588308946517L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java", inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3cf61098f11c..71dd6653f6a6 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -645,6 +645,11 @@ public final class InputMethodManager { @Override public void setCurrentRootView(ViewRootImpl rootView) { synchronized (mH) { + if (mCurRootView != null) { + // Reset the last served view and restart window focus state of the root view. + mCurRootView.getImeFocusController().setServedView(null); + mRestartOnNextWindowFocus = true; + } mCurRootView = rootView; } } diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java index 4f2af63626cf..8657e828a3f6 100644 --- a/core/java/android/widget/inline/InlineContentView.java +++ b/core/java/android/widget/inline/InlineContentView.java @@ -21,40 +21,45 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PixelFormat; import android.util.AttributeSet; +import android.util.Log; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; +import java.util.function.Consumer; + /** - * This class represents a view that holds opaque content from another app that - * you can inline in your UI. + * This class represents a view that holds opaque content from another app that you can inline in + * your UI. * * <p>Since the content presented by this view is from another security domain,it is - * shown on a remote surface preventing the host application from accessing that content. - * Also the host application cannot interact with the inlined content by injecting touch - * events or clicking programmatically. + * shown on a remote surface preventing the host application from accessing that content. Also the + * host application cannot interact with the inlined content by injecting touch events or clicking + * programmatically. * * <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case - * the inined UI would not be interactive. Sometimes this is desirable, e.g. animating - * transitions. + * the inlined UI would not be interactive. Sometimes this is desirable, e.g. animating transitions. * * <p>By default the surface backing this view is shown on top of the hosting window such - * that the inlined content is interactive. However, you can temporarily move the surface - * under the hosting window which could be useful in some cases, e.g. animating transitions. - * At this point the inlined content will not be interactive and the touch events would - * be delivered to your app. - * <p> - * Instances of this class are created by the platform and can be programmatically attached - * to your UI. Once you attach and detach this view it can not longer be reused and you - * should obtain a new view from the platform via the dedicated APIs. + * that the inlined content is interactive. However, you can temporarily move the surface under the + * hosting window which could be useful in some cases, e.g. animating transitions. At this point the + * inlined content will not be interactive and the touch events would be delivered to your app. + * + * <p> Instances of this class are created by the platform and can be programmatically attached to + * your UI. Once the view is attached to the window, you may detach and reattach it to the window. + * It should work seamlessly from the hosting process's point of view. */ public class InlineContentView extends ViewGroup { + private static final String TAG = "InlineContentView"; + + private static final boolean DEBUG = false; + /** - * Callback for observing the lifecycle of the surface control - * that manipulates the backing secure embedded UI surface. + * Callback for observing the lifecycle of the surface control that manipulates the backing + * secure embedded UI surface. */ public interface SurfaceControlCallback { /** @@ -72,15 +77,41 @@ public class InlineContentView extends ViewGroup { void onDestroyed(@NonNull SurfaceControl surfaceControl); } - private final @NonNull SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { + /** + * Callback for sending an updated surface package in case the previous one is released + * from the detached from window event, and for getting notified of such event. + * + * This is expected to be provided to the {@link InlineContentView} so it can get updates + * from and send updates to the remote content (i.e. surface package) provider. + * + * @hide + */ + public interface SurfacePackageUpdater { + + /** + * Called when the previous surface package is released due to view being detached + * from the window. + */ + void onSurfacePackageReleased(); + + /** + * Called to request an updated surface package. + * + * @param consumer consumes the updated surface package. + */ + void getSurfacePackage(Consumer<SurfaceControlViewHost.SurfacePackage> consumer); + } + + @NonNull + private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl()); } @Override - public void surfaceChanged(@NonNull SurfaceHolder holder, - int format, int width, int height) { + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, + int height) { /* do nothing */ } @@ -90,13 +121,17 @@ public class InlineContentView extends ViewGroup { } }; - private final @NonNull SurfaceView mSurfaceView; + @NonNull + private final SurfaceView mSurfaceView; + + @Nullable + private SurfaceControlCallback mSurfaceControlCallback; - private @Nullable SurfaceControlCallback mSurfaceControlCallback; + @Nullable + private SurfacePackageUpdater mSurfacePackageUpdater; /** * @inheritDoc - * * @hide */ public InlineContentView(@NonNull Context context) { @@ -105,7 +140,6 @@ public class InlineContentView extends ViewGroup { /** * @inheritDoc - * * @hide */ public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) { @@ -114,7 +148,6 @@ public class InlineContentView extends ViewGroup { /** * @inheritDoc - * * @hide */ public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, @@ -123,20 +156,18 @@ public class InlineContentView extends ViewGroup { } /** - * Gets the surface control. If the surface is not created this method - * returns {@code null}. + * Gets the surface control. If the surface is not created this method returns {@code null}. * * @return The surface control. - * * @see #setSurfaceControlCallback(SurfaceControlCallback) */ - public @Nullable SurfaceControl getSurfaceControl() { + @Nullable + public SurfaceControl getSurfaceControl() { return mSurfaceView.getSurfaceControl(); } /** * @inheritDoc - * * @hide */ public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, @@ -149,14 +180,35 @@ public class InlineContentView extends ViewGroup { } /** - * Sets the embedded UI. - * @param surfacePackage The embedded UI. + * Sets the embedded UI provider. * * @hide */ - public void setChildSurfacePackage( - @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { - mSurfaceView.setChildSurfacePackage(surfacePackage); + public void setChildSurfacePackageUpdater( + @Nullable SurfacePackageUpdater surfacePackageUpdater) { + mSurfacePackageUpdater = surfacePackageUpdater; + } + + @Override + protected void onAttachedToWindow() { + if (DEBUG) Log.v(TAG, "onAttachedToWindow"); + super.onAttachedToWindow(); + if (mSurfacePackageUpdater != null) { + mSurfacePackageUpdater.getSurfacePackage( + sp -> { + if (DEBUG) Log.v(TAG, "Received new SurfacePackage"); + mSurfaceView.setChildSurfacePackage(sp); + }); + } + } + + @Override + protected void onDetachedFromWindow() { + if (DEBUG) Log.v(TAG, "onDetachedFromWindow"); + super.onDetachedFromWindow(); + if (mSurfacePackageUpdater != null) { + mSurfacePackageUpdater.onSurfacePackageReleased(); + } } @Override @@ -165,8 +217,8 @@ public class InlineContentView extends ViewGroup { } /** - * Sets a callback to observe the lifecycle of the surface control for - * managing the backing surface. + * Sets a callback to observe the lifecycle of the surface control for managing the backing + * surface. * * @param callback The callback to set or {@code null} to clear. */ @@ -182,7 +234,6 @@ public class InlineContentView extends ViewGroup { /** * @return Whether the surface backing this view appears on top of its parent. - * * @see #setZOrderedOnTop(boolean) */ public boolean isZOrderedOnTop() { @@ -190,17 +241,15 @@ public class InlineContentView extends ViewGroup { } /** - * Controls whether the backing surface is placed on top of this view's window. - * Normally, it is placed on top of the window, to allow interaction - * with the inlined UI. Via this method, you can place the surface below the - * window. This means that all of the contents of the window this view is in - * will be visible on top of its surface. + * Controls whether the backing surface is placed on top of this view's window. Normally, it is + * placed on top of the window, to allow interaction with the inlined UI. Via this method, you + * can place the surface below the window. This means that all of the contents of the window + * this view is in will be visible on top of its surface. * * <p> The Z ordering can be changed dynamically if the backing surface is * created, otherwise the ordering would be applied at surface construction time. * * @param onTop Whether to show the surface on top of this view's window. - * * @see #isZOrderedOnTop() */ public boolean setZOrderedOnTop(boolean onTop) { diff --git a/core/java/android/window/TaskEmbedder.java b/core/java/android/window/TaskEmbedder.java index 4257ce084829..ca6c568c2668 100644 --- a/core/java/android/window/TaskEmbedder.java +++ b/core/java/android/window/TaskEmbedder.java @@ -36,11 +36,14 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.VirtualDisplay; +import android.os.RemoteException; import android.os.UserHandle; import android.view.IWindow; import android.view.IWindowManager; +import android.view.IWindowSession; import android.view.KeyEvent; import android.view.SurfaceControl; +import android.view.WindowManagerGlobal; import dalvik.system.CloseGuard; @@ -184,31 +187,45 @@ public abstract class TaskEmbedder { /** * Called when the task embedder should be initialized. + * NOTE: all overriding methods should call this one after they finish their initialization. * @return whether to report whether the embedder was initialized. */ - public abstract boolean onInitialize(); + public boolean onInitialize() { + updateLocationAndTapExcludeRegion(); + return true; + } /** * Called when the task embedder should be released. * @return whether to report whether the embedder was released. */ - protected abstract boolean onRelease(); + protected boolean onRelease() { + // Clear tap-exclude region (if any) for this window. + clearTapExcludeRegion(); + return true; + } /** * Starts presentation of tasks in this container. */ - public abstract void start(); + public void start() { + updateLocationAndTapExcludeRegion(); + } /** * Stops presentation of tasks in this container. */ - public abstract void stop(); + public void stop() { + clearTapExcludeRegion(); + } /** * This should be called whenever the position or size of the surface changes * or if touchable areas above the surface are added or removed. */ - public abstract void notifyBoundsChanged(); + public void notifyBoundsChanged() { + updateLocationAndTapExcludeRegion(); + } /** * Called to update the dimensions whenever the host size changes. @@ -268,6 +285,48 @@ public abstract class TaskEmbedder { } /** + * Updates position and bounds information needed by WM and IME to manage window + * focus and touch events properly. + * <p> + * This should be called whenever the position or size of the surface changes + * or if touchable areas above the surface are added or removed. + */ + protected void updateLocationAndTapExcludeRegion() { + if (!isInitialized() || mHost.getWindow() == null) { + return; + } + applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion()); + } + + /** + * Call to update the tap exclude region for the window. + * <p> + * This should not normally be called directly, but through + * {@link #updateLocationAndTapExcludeRegion()}. This method + * is provided as an optimization when managing multiple TaskSurfaces within a view. + * + * @see IWindowSession#updateTapExcludeRegion(IWindow, Region) + */ + private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) { + try { + IWindowSession session = WindowManagerGlobal.getWindowSession(); + session.updateTapExcludeRegion(window, tapExcludeRegion); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}. + */ + private void clearTapExcludeRegion() { + if (!isInitialized() || mHost.getWindow() == null) { + return; + } + applyTapExcludeRegion(mHost.getWindow(), null); + } + + /** * Set the callback to be notified about state changes. * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called. * <p>Note: If the instance was ready prior to this call being made, then diff --git a/core/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java index 2fb46509f337..1b87521f3a96 100644 --- a/core/java/android/window/TaskOrganizerTaskEmbedder.java +++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java @@ -75,7 +75,8 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { // infrastructure is ready. mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true); - return true; + + return super.onInitialize(); } @Override @@ -96,6 +97,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { */ @Override public void start() { + super.start(); if (DEBUG) { log("start"); } @@ -119,6 +121,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { */ @Override public void stop() { + super.stop(); if (DEBUG) { log("stop"); } @@ -143,6 +146,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { */ @Override public void notifyBoundsChanged() { + super.notifyBoundsChanged(); if (DEBUG) { log("notifyBoundsChanged: screenBounds=" + mHost.getScreenBounds()); } diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index 6f85dc263a4d..2e6cbeee7d22 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -21,7 +21,6 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_C import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.view.Display.INVALID_DISPLAY; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -40,7 +39,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; -import android.view.IWindow; import android.view.IWindowManager; import android.view.IWindowSession; import android.view.InputDevice; @@ -134,20 +132,15 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { e.rethrowAsRuntimeException(); } - if (mHost.getWindow() != null) { - updateLocationAndTapExcludeRegion(); - } - return true; + return super.onInitialize(); } @Override protected boolean onRelease() { + super.onRelease(); // Clear activity view geometry for IME on this display clearActivityViewGeometryForIme(); - // Clear tap-exclude region (if any) for this window. - clearTapExcludeRegion(); - if (mTaskStackListener != null) { try { mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); @@ -170,9 +163,9 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { */ @Override public void start() { + super.start(); if (isInitialized()) { mVirtualDisplay.setDisplayState(true); - updateLocationAndTapExcludeRegion(); } } @@ -181,23 +174,14 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { */ @Override public void stop() { + super.stop(); if (isInitialized()) { mVirtualDisplay.setDisplayState(false); clearActivityViewGeometryForIme(); - clearTapExcludeRegion(); } } /** - * This should be called whenever the position or size of the surface changes - * or if touchable areas above the surface are added or removed. - */ - @Override - public void notifyBoundsChanged() { - updateLocationAndTapExcludeRegion(); - } - - /** * Called to update the dimensions whenever the host size changes. * * @param width the new width of the surface @@ -298,12 +282,13 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { * This should be called whenever the position or size of the surface changes * or if touchable areas above the surface are added or removed. */ - private void updateLocationAndTapExcludeRegion() { + @Override + protected void updateLocationAndTapExcludeRegion() { + super.updateLocationAndTapExcludeRegion(); if (!isInitialized() || mHost.getWindow() == null) { return; } reportLocation(mHost.getScreenToTaskMatrix(), mHost.getPositionInWindow()); - applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion()); } /** @@ -332,24 +317,6 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { } /** - * Call to update the tap exclude region for the window. - * <p> - * This should not normally be called directly, but through - * {@link #updateLocationAndTapExcludeRegion()}. This method - * is provided as an optimization when managing multiple TaskSurfaces within a view. - * - * @see IWindowSession#updateTapExcludeRegion(IWindow, Region) - */ - private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) { - try { - IWindowSession session = WindowManagerGlobal.getWindowSession(); - session.updateTapExcludeRegion(window, tapExcludeRegion); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } - } - - /** * @see InputMethodManager#reportActivityView(int, Matrix) */ private void clearActivityViewGeometryForIme() { @@ -357,17 +324,6 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null); } - /** - * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}. - */ - private void clearTapExcludeRegion() { - if (mHost.getWindow() == null) { - Log.w(TAG, "clearTapExcludeRegion: not attached to window!"); - return; - } - applyTapExcludeRegion(mHost.getWindow(), null); - } - private static KeyEvent createKeyEvent(int action, int code, int displayId) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index be06913c78e5..3fc3f3e65d37 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -158,6 +158,7 @@ public class ChooserActivity extends ResolverActivity implements private static final String TAG = "ChooserActivity"; private AppPredictor mPersonalAppPredictor; private AppPredictor mWorkAppPredictor; + private boolean mShouldDisplayLandscape; @UnsupportedAppUsage public ChooserActivity() { @@ -253,7 +254,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED, - false); + true); private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; @@ -716,6 +717,8 @@ public class ChooserActivity extends ResolverActivity implements mCallerChooserTargets = targets; } + mShouldDisplayLandscape = shouldDisplayLandscape( + getResources().getConfiguration().orientation); setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false)); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); @@ -884,7 +887,8 @@ public class ChooserActivity extends ResolverActivity implements /* context */ this, adapter, getPersonalProfileUserHandle(), - /* workProfileUserHandle= */ null); + /* workProfileUserHandle= */ null, + isSendAction(getTargetIntent())); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( @@ -914,7 +918,8 @@ public class ChooserActivity extends ResolverActivity implements workAdapter, selectedProfile, getPersonalProfileUserHandle(), - getWorkProfileUserHandle()); + getWorkProfileUserHandle(), + isSendAction(getTargetIntent())); } private int findSelectedProfile() { @@ -1071,6 +1076,7 @@ public class ChooserActivity extends ResolverActivity implements public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); adjustPreviewWidth(newConfig.orientation, null); updateStickyContentPreview(); } @@ -1084,7 +1090,7 @@ public class ChooserActivity extends ResolverActivity implements private void adjustPreviewWidth(int orientation, View parent) { int width = -1; - if (shouldDisplayLandscape(orientation)) { + if (mShouldDisplayLandscape) { width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width); } @@ -2938,6 +2944,19 @@ public class ChooserActivity extends ResolverActivity implements .setSubtype(previewType)); } + class ViewHolderBase extends RecyclerView.ViewHolder { + private int mViewType; + + ViewHolderBase(View itemView, int viewType) { + super(itemView); + this.mViewType = viewType; + } + + int getViewType() { + return mViewType; + } + } + /** * Used to bind types of individual item including * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL}, @@ -2945,12 +2964,12 @@ public class ChooserActivity extends ResolverActivity implements * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE}, * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}. */ - final class ItemViewHolder extends RecyclerView.ViewHolder { + final class ItemViewHolder extends ViewHolderBase { ResolverListAdapter.ViewHolder mWrappedViewHolder; int mListPosition = ChooserListAdapter.NO_POSITION; - ItemViewHolder(View itemView, boolean isClickable) { - super(itemView); + ItemViewHolder(View itemView, boolean isClickable, int viewType) { + super(itemView, viewType); mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView); if (isClickable) { itemView.setOnClickListener(v -> startSelected(mListPosition, @@ -2968,9 +2987,9 @@ public class ChooserActivity extends ResolverActivity implements /** * Add a footer to the list, to support scrolling behavior below the navbar. */ - final class FooterViewHolder extends RecyclerView.ViewHolder { - FooterViewHolder(View itemView) { - super(itemView); + final class FooterViewHolder extends ViewHolderBase { + FooterViewHolder(View itemView, int viewType) { + super(itemView, viewType); } } @@ -3081,7 +3100,7 @@ public class ChooserActivity extends ResolverActivity implements int getMaxTargetsPerRow() { int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT; - if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) { + if (mShouldDisplayLandscape) { maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE; } return maxTargets; @@ -3189,13 +3208,14 @@ public class ChooserActivity extends ResolverActivity implements public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_CONTENT_PREVIEW: - return new ItemViewHolder(createContentPreviewView(parent), false); + return new ItemViewHolder(createContentPreviewView(parent), false, viewType); case VIEW_TYPE_PROFILE: - return new ItemViewHolder(createProfileView(parent), false); + return new ItemViewHolder(createProfileView(parent), false, viewType); case VIEW_TYPE_AZ_LABEL: - return new ItemViewHolder(createAzLabelView(parent), false); + return new ItemViewHolder(createAzLabelView(parent), false, viewType); case VIEW_TYPE_NORMAL: - return new ItemViewHolder(mChooserListAdapter.createView(parent), true); + return new ItemViewHolder( + mChooserListAdapter.createView(parent), true, viewType); case VIEW_TYPE_DIRECT_SHARE: case VIEW_TYPE_CALLER_AND_RANK: return createItemGroupViewHolder(viewType, parent); @@ -3203,7 +3223,7 @@ public class ChooserActivity extends ResolverActivity implements Space sp = new Space(parent.getContext()); sp.setLayoutParams(new RecyclerView.LayoutParams( LayoutParams.MATCH_PARENT, mFooterHeight)); - return new FooterViewHolder(sp); + return new FooterViewHolder(sp, viewType); default: // Since we catch all possible viewTypes above, no chance this is being called. return null; @@ -3212,7 +3232,7 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - int viewType = getItemViewType(position); + int viewType = ((ViewHolderBase) holder).getViewType(); switch (viewType) { case VIEW_TYPE_DIRECT_SHARE: case VIEW_TYPE_CALLER_AND_RANK: @@ -3323,7 +3343,6 @@ public class ChooserActivity extends ResolverActivity implements } viewGroup.setTag(holder); - return holder; } @@ -3350,14 +3369,15 @@ public class ChooserActivity extends ResolverActivity implements parentGroup.addView(row2); mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, - Lists.newArrayList(row1, row2), getMaxTargetsPerRow()); + Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType); loadViewsIntoGroup(mDirectShareViewHolder); return mDirectShareViewHolder; } else { ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, false); - ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow()); + ItemGroupViewHolder holder = + new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType); loadViewsIntoGroup(holder); return holder; @@ -3519,14 +3539,14 @@ public class ChooserActivity extends ResolverActivity implements * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE}, * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}. */ - abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder { + abstract class ItemGroupViewHolder extends ViewHolderBase { protected int mMeasuredRowHeight; private int[] mItemIndices; protected final View[] mCells; private final int mColumnCount; - ItemGroupViewHolder(int cellCount, View itemView) { - super(itemView); + ItemGroupViewHolder(int cellCount, View itemView, int viewType) { + super(itemView, viewType); this.mCells = new View[cellCount]; this.mItemIndices = new int[cellCount]; this.mColumnCount = cellCount; @@ -3572,8 +3592,8 @@ public class ChooserActivity extends ResolverActivity implements class SingleRowViewHolder extends ItemGroupViewHolder { private final ViewGroup mRow; - SingleRowViewHolder(ViewGroup row, int cellCount) { - super(cellCount, row); + SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) { + super(cellCount, row, viewType); this.mRow = row; } @@ -3615,8 +3635,9 @@ public class ChooserActivity extends ResolverActivity implements private final boolean[] mCellVisibility; - DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) { - super(rows.size() * cellCountPerRow, parent); + DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow, + int viewType) { + super(rows.size() * cellCountPerRow, parent, viewType); this.mParent = parent; this.mRows = rows; diff --git a/core/java/com/android/internal/app/ChooserGridLayoutManager.java b/core/java/com/android/internal/app/ChooserGridLayoutManager.java new file mode 100644 index 000000000000..317a987cf359 --- /dev/null +++ b/core/java/com/android/internal/app/ChooserGridLayoutManager.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.internal.widget.GridLayoutManager; +import com.android.internal.widget.RecyclerView; + +/** + * For a11y and per {@link RecyclerView#onInitializeAccessibilityNodeInfo}, override + * methods to ensure proper row counts. + */ +public class ChooserGridLayoutManager extends GridLayoutManager { + + /** + * Constructor used when layout manager is set in XML by RecyclerView attribute + * "layoutManager". If spanCount is not specified in the XML, it defaults to a + * single column. + * + */ + public ChooserGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * Creates a vertical GridLayoutManager + * + * @param context Current context, will be used to access resources. + * @param spanCount The number of columns in the grid + */ + public ChooserGridLayoutManager(Context context, int spanCount) { + super(context, spanCount); + } + + /** + * @param context Current context, will be used to access resources. + * @param spanCount The number of columns or rows in the grid + * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link + * #VERTICAL}. + * @param reverseLayout When set to true, layouts from end to start. + */ + public ChooserGridLayoutManager(Context context, int spanCount, int orientation, + boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + @Override + public int getRowCountForAccessibility(RecyclerView.Recycler recycler, + RecyclerView.State state) { + // Do not count the footer view in the official count + return super.getRowCountForAccessibility(recycler, state) - 1; + } +} diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 57157f7b1f5b..774be3c9c4b8 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -37,15 +37,18 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd private static final int SINGLE_CELL_SPAN_SIZE = 1; private final ChooserProfileDescriptor[] mItems; + private final boolean mIsSendAction; ChooserMultiProfilePagerAdapter(Context context, ChooserActivity.ChooserGridAdapter adapter, UserHandle personalProfileUserHandle, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + boolean isSendAction) { super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle); mItems = new ChooserProfileDescriptor[] { createProfileDescriptor(adapter) }; + mIsSendAction = isSendAction; } ChooserMultiProfilePagerAdapter(Context context, @@ -53,13 +56,15 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd ChooserActivity.ChooserGridAdapter workAdapter, @Profile int defaultProfile, UserHandle personalProfileUserHandle, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + boolean isSendAction) { super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, workProfileUserHandle); mItems = new ChooserProfileDescriptor[] { createProfileDescriptor(personalAdapter), createProfileDescriptor(workAdapter) }; + mIsSendAction = isSendAction; } private ChooserProfileDescriptor createProfileDescriptor( @@ -182,34 +187,62 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd @Override protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) { - showEmptyState(activeListAdapter, - R.drawable.ic_sharing_disabled, - R.string.resolver_cant_share_with_work_apps, - R.string.resolver_cant_share_with_work_apps_explanation); + if (mIsSendAction) { + showEmptyState(activeListAdapter, + R.drawable.ic_sharing_disabled, + R.string.resolver_cant_share_with_work_apps, + R.string.resolver_cant_share_with_work_apps_explanation); + } else { + showEmptyState(activeListAdapter, + R.drawable.ic_sharing_disabled, + R.string.resolver_cant_access_work_apps, + R.string.resolver_cant_access_work_apps_explanation); + } } @Override protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) { - showEmptyState(activeListAdapter, - R.drawable.ic_sharing_disabled, - R.string.resolver_cant_share_with_personal_apps, - R.string.resolver_cant_share_with_personal_apps_explanation); + if (mIsSendAction) { + showEmptyState(activeListAdapter, + R.drawable.ic_sharing_disabled, + R.string.resolver_cant_share_with_personal_apps, + R.string.resolver_cant_share_with_personal_apps_explanation); + } else { + showEmptyState(activeListAdapter, + R.drawable.ic_sharing_disabled, + R.string.resolver_cant_access_personal_apps, + R.string.resolver_cant_access_personal_apps_explanation); + } } @Override protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available_share, - /* subtitleRes */ 0); + if (mIsSendAction) { + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_personal_apps_available_share, + /* subtitleRes */ 0); + } else { + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_personal_apps_available_resolve, + /* subtitleRes */ 0); + } } @Override protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available_share, - /* subtitleRes */ 0); + if (mIsSendAction) { + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_work_apps_available_share, + /* subtitleRes */ 0); + } else { + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_work_apps_available_resolve, + /* subtitleRes */ 0); + } } class ChooserProfileDescriptor extends ProfileDescriptor { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 2f62f8e7a0c9..83dabe8d0525 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -182,6 +182,8 @@ public class ResolverActivity extends Activity implements private BroadcastReceiver mWorkProfileStateReceiver; private UserHandle mHeaderCreatorUser; + private UserHandle mWorkProfileUserHandle; + /** * Get the string resource to be used as a label for the link to the resolver activity for an * action. @@ -363,6 +365,7 @@ public class ResolverActivity extends Activity implements // a more complicated UI that the current voice interaction flow is not able // to handle. boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction(); + mWorkProfileUserHandle = fetchWorkProfileUserProfile(); mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); if (configureContentView()) { return; @@ -527,13 +530,18 @@ public class ResolverActivity extends Activity implements return UserHandle.of(ActivityManager.getCurrentUser()); } protected @Nullable UserHandle getWorkProfileUserHandle() { + return mWorkProfileUserHandle; + } + + protected @Nullable UserHandle fetchWorkProfileUserProfile() { + mWorkProfileUserHandle = null; UserManager userManager = getSystemService(UserManager.class); for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) { if (userInfo.isManagedProfile()) { - return userInfo.getUserHandle(); + mWorkProfileUserHandle = userInfo.getUserHandle(); } } - return null; + return mWorkProfileUserHandle; } private boolean hasWorkProfile() { diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 2fd938f45291..24bf98b6502c 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -54,6 +54,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.TargetInfo; import java.util.ArrayList; @@ -549,6 +550,15 @@ public class ResolverListAdapter extends BaseAdapter { getLoadLabelTask((DisplayResolveInfo) info, holder).execute(); } else { holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo()); + if (info instanceof SelectableTargetInfo) { + // direct share targets should append the application name for a better readout + DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo(); + CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : ""; + CharSequence extendedInfo = info.getExtendedInfo(); + String contentDescription = String.join(" ", info.getDisplayLabel(), + extendedInfo != null ? extendedInfo : "", appName); + holder.updateContentDescription(contentDescription); + } } if (info.isSuspended()) { @@ -697,6 +707,12 @@ public class ResolverListAdapter extends BaseAdapter { text2.setVisibility(View.VISIBLE); text2.setText(subLabel); } + + itemView.setContentDescription(null); + } + + public void updateContentDescription(String description) { + itemView.setContentDescription(description); } } diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java index 246a07d3d0fe..900e18d468bb 100644 --- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java +++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java @@ -44,7 +44,6 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett import com.android.internal.app.SimpleIconFactory; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -136,6 +135,10 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { return mIsSuspended; } + public DisplayResolveInfo getDisplayResolveInfo() { + return mSourceInfo; + } + private Drawable getChooserTargetIconDrawable(ChooserTarget target, @Nullable ShortcutInfo shortcutInfo) { Drawable directShareIcon = null; diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index 3900f1674c13..7195b45a4055 100644 --- a/core/java/com/android/internal/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -321,6 +321,20 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest)); } + /** + * Executes an async request immediately instead of sending it to Handler queue as what + * {@link scheduleAsyncRequest} does. + * + * <p>This request is not expecting a callback from the service, hence it's represented by + * a simple {@link Runnable}. + */ + protected void executeAsyncRequest(@NonNull AsyncRequest<I> request) { + // TODO(b/117779333): fix generics below + @SuppressWarnings({"unchecked", "rawtypes"}) + final MyAsyncPendingRequest<S, I> asyncRequest = new MyAsyncPendingRequest(this, request); + handlePendingRequest(asyncRequest); + } + private void cancelScheduledUnbind() { mHandler.removeMessages(MSG_UNBIND); } diff --git a/core/java/com/android/internal/policy/TaskResizingAlgorithm.java b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java index 1fce098fb93e..1ec020696cf3 100644 --- a/core/java/com/android/internal/policy/TaskResizingAlgorithm.java +++ b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java @@ -91,14 +91,14 @@ public class TaskResizingAlgorithm { int width = right - left; int height = bottom - top; if ((ctrlType & CTRL_LEFT) != 0) { - width = Math.max(minVisibleWidth, width - deltaX); + width = Math.max(minVisibleWidth, Math.min(width - deltaX, maxVisibleSize.x)); } else if ((ctrlType & CTRL_RIGHT) != 0) { - width = Math.max(minVisibleWidth, width + deltaX); + width = Math.max(minVisibleWidth, Math.min(width + deltaX, maxVisibleSize.x)); } if ((ctrlType & CTRL_TOP) != 0) { - height = Math.max(minVisibleHeight, height - deltaY); + height = Math.max(minVisibleHeight, Math.min(height - deltaY, maxVisibleSize.y)); } else if ((ctrlType & CTRL_BOTTOM) != 0) { - height = Math.max(minVisibleHeight, height + deltaY); + height = Math.max(minVisibleHeight, Math.min(height + deltaY, maxVisibleSize.y)); } // If we have to preserve the orientation - check that we are doing so. diff --git a/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl b/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl index 08a349c21c8b..78df3eb660a5 100644 --- a/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl +++ b/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl @@ -24,4 +24,6 @@ import com.android.internal.view.inline.IInlineContentCallback; */ oneway interface IInlineContentProvider { void provideContent(int width, int height, in IInlineContentCallback callback); + void requestSurfacePackage(); + void onSurfacePackageReleased(); } diff --git a/core/java/com/android/internal/widget/GridLayoutManager.java b/core/java/com/android/internal/widget/GridLayoutManager.java index e0502f129f7f..09e6a991b1ac 100644 --- a/core/java/com/android/internal/widget/GridLayoutManager.java +++ b/core/java/com/android/internal/widget/GridLayoutManager.java @@ -153,13 +153,11 @@ public class GridLayoutManager extends LinearLayoutManager { if (mOrientation == HORIZONTAL) { info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain( glp.getSpanIndex(), glp.getSpanSize(), - spanGroupIndex, 1, - mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false)); + spanGroupIndex, 1, false, false)); } else { // VERTICAL info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain( spanGroupIndex, 1, - glp.getSpanIndex(), glp.getSpanSize(), - mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false)); + glp.getSpanIndex(), glp.getSpanSize(), false, false)); } } diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index fb2ecf3a478f..3f708f84750c 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -825,18 +825,6 @@ public class ResolverDrawerLayout extends ViewGroup { return true; } break; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: - case R.id.accessibilityActionScrollUp: - if (mCollapseOffset < mCollapsibleHeight) { - smoothScrollTo(mCollapsibleHeight, 0); - return true; - } else if ((mCollapseOffset < mCollapsibleHeight + mUncollapsibleHeight) - && isDismissable()) { - smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, 0); - mDismissOnScrollerFinished = true; - return true; - } - break; case AccessibilityNodeInfo.ACTION_COLLAPSE: if (mCollapseOffset < mCollapsibleHeight) { smoothScrollTo(mCollapsibleHeight, 0); @@ -886,7 +874,6 @@ public class ResolverDrawerLayout extends ViewGroup { } if ((mCollapseOffset < mCollapsibleHeight + mUncollapsibleHeight) && ((mCollapseOffset < mCollapsibleHeight) || isDismissable())) { - info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); info.addAction(AccessibilityAction.ACTION_SCROLL_UP); info.setScrollable(true); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 3a5720fd8c4c..c5bc083dfabf 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1552,8 +1552,8 @@ static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list, } } -static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_name, - const char* package, fail_fn_t fail_fn) { +static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid, + const char* dir_name, const char* package, fail_fn_t fail_fn) { bool hasSdcardFs = IsFilesystemSupported("sdcardfs"); std::string source; @@ -1565,6 +1565,9 @@ static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_na } std::string target = StringPrintf("/storage/emulated/%d/%s/%s", user_id, dir_name, package); + // As the parent is mounted as tmpfs, we need to create the target dir here. + PrepareDirIfNotPresent(target, 0700, uid, uid, fail_fn); + if (access(source.c_str(), F_OK) != 0) { fail_fn(CREATE_ERROR("Error accessing %s: %s", source.c_str(), strerror(errno))); } @@ -1574,9 +1577,8 @@ static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_na BindMount(source, target, fail_fn); } -// Bind mount all obb & data directories that are visible to this app. -// If app data isolation is not enabled for this process, bind mount the whole obb -// and data directory instead. +// Mount tmpfs on Android/data and Android/obb, then bind mount all app visible package +// directories in data and obb directories. static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list, uid_t uid, const char* process_name, jstring managed_nice_name, fail_fn_t fail_fn) { @@ -1590,12 +1592,18 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list, fail_fn(CREATE_ERROR("Data package list cannot be empty")); } + // Create tmpfs on Android/obb and Android/data so these 2 dirs won't enter fuse anymore. + std::string androidObbDir = StringPrintf("/storage/emulated/%d/Android/obb", user_id); + MountAppDataTmpFs(androidObbDir, fail_fn); + std::string androidDataDir = StringPrintf("/storage/emulated/%d/Android/data", user_id); + MountAppDataTmpFs(androidDataDir, fail_fn); + // Bind mount each package obb directory for (int i = 0; i < size; i += 3) { jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); std::string packageName = extract_fn(package_str).value(); - BindMountStorageToLowerFs(user_id, "Android/obb", packageName.c_str(), fail_fn); - BindMountStorageToLowerFs(user_id, "Android/data", packageName.c_str(), fail_fn); + BindMountStorageToLowerFs(user_id, uid, "Android/obb", packageName.c_str(), fail_fn); + BindMountStorageToLowerFs(user_id, uid, "Android/data", packageName.c_str(), fail_fn); } } @@ -1648,9 +1656,10 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, uid, process_name, managed_nice_name, fail_fn); isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); } - if (mount_external != MOUNT_EXTERNAL_INSTALLER && - mount_external != MOUNT_EXTERNAL_PASS_THROUGH && - mount_storage_dirs) { + // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps + // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()), + // and hence they won't bind mount storage dirs. + if (mount_storage_dirs) { BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); } diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index d5384a1c2fdd..762895b6320f 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -169,6 +169,7 @@ message GlobalSettingsProto { optional SettingProto boot_count = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto bugreport_in_power_menu = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cached_apps_freezer_enabled = 152 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto call_auto_retry = 24 [ (android.privacy).dest = DEST_AUTOMATIC ]; message CaptivePortal { @@ -1059,5 +1060,5 @@ message GlobalSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 152; + // Next tag = 153; } diff --git a/core/res/res/anim/dream_activity_close_exit.xml b/core/res/res/anim/dream_activity_close_exit.xml new file mode 100644 index 000000000000..c4599dad31a0 --- /dev/null +++ b/core/res/res/anim/dream_activity_close_exit.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:duration="100" /> + diff --git a/core/res/res/anim/dream_activity_open_enter.xml b/core/res/res/anim/dream_activity_open_enter.xml new file mode 100644 index 000000000000..9e1c6e2ee0d7 --- /dev/null +++ b/core/res/res/anim/dream_activity_open_enter.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- During this animation we keep the previous activity on the screen +using a noop animation for it (dream_activity_open_exit). The duration of +those two has to be the same. --> +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="0.0" + android:toAlpha="1.0" + android:duration="1000" /> + diff --git a/core/res/res/anim/dream_activity_open_exit.xml b/core/res/res/anim/dream_activity_open_exit.xml new file mode 100644 index 000000000000..740f52856b7f --- /dev/null +++ b/core/res/res/anim/dream_activity_open_exit.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- A noop animation to keep the previous activity alive during the dream +enter animation. The duration should match the duration of the +dream_activity_open_enter animation. --> +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:duration="1000" /> diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml index 6b1b002267cb..86dc71cbbfb8 100644 --- a/core/res/res/layout/chooser_list_per_profile.xml +++ b/core/res/res/layout/chooser_list_per_profile.xml @@ -20,7 +20,7 @@ <com.android.internal.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" - android:layoutManager="com.android.internal.widget.GridLayoutManager" + android:layoutManager="com.android.internal.app.ChooserGridLayoutManager" android:id="@+id/resolver_list" android:clipToPadding="false" android:background="?attr/colorBackgroundFloating" @@ -29,4 +29,4 @@ android:nestedScrollingEnabled="true" /> <include layout="@layout/resolver_empty_states" /> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 4d0837f495df..446ce3fbaf4b 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -83,6 +83,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" + android:accessibilityTraversalAfter="@id/title" android:background="?attr/colorBackgroundFloating"> <LinearLayout android:orientation="vertical" diff --git a/core/res/res/values-mcc334-mnc020/config.xml b/core/res/res/values-mcc334-mnc020/config.xml index 0970517835b6..c64acc7c29db 100644 --- a/core/res/res/values-mcc334-mnc020/config.xml +++ b/core/res/res/values-mcc334-mnc020/config.xml @@ -18,4 +18,7 @@ --> <resources> <bool name="config_use_sim_language_file">false</bool> + + <bool name="config_pdp_rejeect_enable_retry">true</bool> + <integer name="config_pdp_reject_retry_delay_ms">45000</integer> </resources>
\ No newline at end of file diff --git a/core/res/res/values-mcc334-mnc020/strings.xml b/core/res/res/values-mcc334-mnc020/strings.xml new file mode 100644 index 000000000000..a8a78d5ef3fc --- /dev/null +++ b/core/res/res/values-mcc334-mnc020/strings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="config_pdp_reject_dialog_title"></string> + <string name="config_pdp_reject_user_authentication_failed">AUTHENTICATION FAILURE -29-</string> + <string name="config_pdp_reject_service_not_subscribed">NOT SUBSCRIBED TO SERVICE -33-</string> + <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed">Multiple PDN connections for a given APN not allowed -55-</string> +</resources>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fa4c25ad460a..65fa3fa1ee1d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4421,4 +4421,8 @@ <!-- Set to true to make assistant show in front of the dream/screensaver. --> <bool name="config_assistantOnTopOfDream">false</bool> + <!-- pdp data retry for cause 29, 33 and 55 --> + <bool name="config_pdp_reject_enable_retry">false</bool> + <!-- pdp data reject retry delay in ms --> + <integer name="config_pdp_reject_retry_delay_ms">-1</integer> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 51b23dbfb59b..35a7857b839f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5747,4 +5747,13 @@ ul.</string> <string name="PERSOSUBSTATE_SIM_IMPI_SUCCESS">IMPI unlock successful.</string> <!-- Success message displayed on SIM NS_SP Depersonalization panel [CHAR LIMIT=none] --> <string name="PERSOSUBSTATE_SIM_NS_SP_SUCCESS">Network subset service provider unlock successful.</string> + + <!-- pdp data reject dialog string for cause 29, 33 and 55 [CHAR LIMIT=100] --> + <string name="config_pdp_reject_dialog_title"></string> + <!-- pdp data reject dialog string for cause 29 (USER_AUTHENTICATION) [CHAR LIMIT=100] --> + <string name="config_pdp_reject_user_authentication_failed"></string> + <!-- pdp data reject dialog string for cause 33 (SERVICE_OPTION_NOT_SUBSCRIBED) [CHAR LIMIT=100] --> + <string name="config_pdp_reject_service_not_subscribed"></string> + <!-- pdp data reject dialog string for cause 55 (MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED) [CHAR LIMIT=100] --> + <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed"></string> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index bcce1f05f0dd..f920083f5cb3 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -248,12 +248,6 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/fast_fade_out</item> </style> - <!-- Window animations for screen savers. {@hide} --> - <style name="Animation.Dream"> - <item name="windowEnterAnimation">@anim/slow_fade_in</item> - <item name="windowExitAnimation">@anim/fast_fade_out</item> - </style> - <!-- Status Bar Styles --> <style name="TextAppearance.StatusBar"> <item name="textAppearance">?attr/textAppearanceSmall</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a82e7784b167..3ac2dc54c00a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1580,7 +1580,6 @@ <java-symbol type="style" name="Animation.Tooltip" /> <java-symbol type="style" name="Animation.TypingFilter" /> <java-symbol type="style" name="Animation.TypingFilterRestore" /> - <java-symbol type="style" name="Animation.Dream" /> <java-symbol type="style" name="Theme.DeviceDefault.Dialog.Alert" /> <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" /> <java-symbol type="style" name="Theme.Dialog.Alert" /> @@ -1825,6 +1824,9 @@ <java-symbol type="anim" name="rotation_animation_jump_exit" /> <java-symbol type="anim" name="rotation_animation_xfade_exit" /> <java-symbol type="anim" name="rotation_animation_enter" /> + <java-symbol type="anim" name="dream_activity_open_exit" /> + <java-symbol type="anim" name="dream_activity_open_enter" /> + <java-symbol type="anim" name="dream_activity_close_exit" /> <java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" /> <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" /> <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" /> @@ -3999,4 +4001,11 @@ <java-symbol type="string" name="notification_channel_network_alerts" /> <java-symbol type="string" name="notification_channel_network_available" /> + <!-- For Pdn throttle feature --> + <java-symbol type="bool" name="config_pdp_reject_enable_retry" /> + <java-symbol type="integer" name="config_pdp_reject_retry_delay_ms" /> + <java-symbol type="string" name="config_pdp_reject_dialog_title" /> + <java-symbol type="string" name="config_pdp_reject_user_authentication_failed" /> + <java-symbol type="string" name="config_pdp_reject_service_not_subscribed" /> + <java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" /> </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 88f9fc2199e5..6e2995de0fe1 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -704,6 +704,7 @@ please see themes_device_defaults.xml. <style name="Theme.Dream"> <item name="windowBackground">@color/black</item> <item name="windowDisablePreview">true</item> + <item name="windowActivityTransitions">true</item> </style> <!-- Default theme for dialog windows and activities (on API level 10 and lower), diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java index 567552f66b35..6720ed6b7bf9 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java @@ -35,6 +35,9 @@ import android.content.IntentSender; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.net.Uri; @@ -300,7 +303,8 @@ public class PackageManagerTests extends AndroidTestCase { final Intent result = localReceiver.getResult(); final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS); - assertEquals(expectedResult, status); + String statusMessage = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); + assertEquals(statusMessage, expectedResult, status); } catch (IllegalArgumentException | IOException | RemoteException e) { Log.w(TAG, "Failed to install package; path=" + inPath, e); fail("Failed to install package; path=" + inPath + ", e=" + e); @@ -327,13 +331,14 @@ public class PackageManagerTests extends AndroidTestCase { return Uri.fromFile(outFile); } - private PackageParser.Package parsePackage(Uri packageURI) throws PackageParserException { + private ParsingPackage parsePackage(Uri packageURI) { final String archiveFilePath = packageURI.getPath(); - PackageParser packageParser = new PackageParser(); - File sourceFile = new File(archiveFilePath); - PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, 0); - packageParser = null; - return pkg; + ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime( + new File(archiveFilePath), 0 /*flags*/, false /*collectCertificates*/); + if (result.isError()) { + throw new IllegalStateException(result.getErrorMessage(), result.getException()); + } + return result.getResult(); } private boolean checkSd(long pkgLen) { @@ -417,9 +422,9 @@ public class PackageManagerTests extends AndroidTestCase { return INSTALL_LOC_ERR; } - private void assertInstall(PackageParser.Package pkg, int flags, int expInstallLocation) { + private void assertInstall(ParsingPackage pkg, int flags, int expInstallLocation) { try { - String pkgName = pkg.packageName; + String pkgName = pkg.getPackageName(); ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0); assertNotNull(info); assertEquals(pkgName, info.packageName); @@ -565,20 +570,20 @@ public class PackageManagerTests extends AndroidTestCase { class InstallParams { Uri packageURI; - PackageParser.Package pkg; + ParsingPackage pkg; InstallParams(String outFileName, int rawResId) throws PackageParserException { this.pkg = getParsedPackage(outFileName, rawResId); - this.packageURI = Uri.fromFile(new File(pkg.codePath)); + this.packageURI = Uri.fromFile(new File(pkg.getCodePath())); } - InstallParams(PackageParser.Package pkg) { - this.packageURI = Uri.fromFile(new File(pkg.codePath)); + InstallParams(ParsingPackage pkg) { + this.packageURI = Uri.fromFile(new File(pkg.getCodePath())); this.pkg = pkg; } long getApkSize() { - File file = new File(pkg.codePath); + File file = new File(pkg.getCodePath()); return file.length(); } } @@ -680,14 +685,12 @@ public class PackageManagerTests extends AndroidTestCase { } } - private PackageParser.Package getParsedPackage(String outFileName, int rawResId) - throws PackageParserException { + private ParsingPackage getParsedPackage(String outFileName, int rawResId) { PackageManager pm = mContext.getPackageManager(); File filesDir = mContext.getFilesDir(); File outFile = new File(filesDir, outFileName); Uri packageURI = getInstallablePackage(rawResId, outFile); - PackageParser.Package pkg = parsePackage(packageURI); - return pkg; + return parsePackage(packageURI); } /* @@ -698,15 +701,15 @@ public class PackageManagerTests extends AndroidTestCase { private void installFromRawResource(InstallParams ip, int flags, boolean cleanUp, boolean fail, int result, int expInstallLocation) throws Exception { PackageManager pm = mContext.getPackageManager(); - PackageParser.Package pkg = ip.pkg; + ParsingPackage pkg = ip.pkg; Uri packageURI = ip.packageURI; if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) { // Make sure the package doesn't exist try { - ApplicationInfo appInfo = pm.getApplicationInfo(pkg.packageName, + ApplicationInfo appInfo = pm.getApplicationInfo(pkg.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES); - GenericReceiver receiver = new DeleteReceiver(pkg.packageName); - invokeDeletePackage(pkg.packageName, 0, receiver); + GenericReceiver receiver = new DeleteReceiver(pkg.getPackageName()); + invokeDeletePackage(pkg.getPackageName(), 0, receiver); } catch (IllegalArgumentException | NameNotFoundException e) { } } @@ -714,10 +717,10 @@ public class PackageManagerTests extends AndroidTestCase { if (fail) { invokeInstallPackageFail(packageURI, flags, result); if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) { - assertNotInstalled(pkg.packageName); + assertNotInstalled(pkg.getPackageName()); } } else { - InstallReceiver receiver = new InstallReceiver(pkg.packageName); + InstallReceiver receiver = new InstallReceiver(pkg.getPackageName()); invokeInstallPackage(packageURI, flags, receiver, true); // Verify installed information assertInstall(pkg, flags, expInstallLocation); @@ -818,15 +821,15 @@ public class PackageManagerTests extends AndroidTestCase { Log.i(TAG, "replace=" + replace); GenericReceiver receiver; if (replace) { - receiver = new ReplaceReceiver(ip.pkg.packageName); + receiver = new ReplaceReceiver(ip.pkg.getPackageName()); Log.i(TAG, "Creating replaceReceiver"); } else { - receiver = new InstallReceiver(ip.pkg.packageName); + receiver = new InstallReceiver(ip.pkg.getPackageName()); } try { invokeInstallPackage(ip.packageURI, flags, receiver, true); if (replace) { - assertInstall(ip.pkg, flags, ip.pkg.installLocation); + assertInstall(ip.pkg, flags, ip.pkg.getInstallLocation()); } } finally { cleanUpInstall(ip); @@ -957,20 +960,20 @@ public class PackageManagerTests extends AndroidTestCase { public void deleteFromRawResource(int iFlags, int dFlags) throws Exception { InstallParams ip = sampleInstallFromRawResource(iFlags, false); boolean retainData = ((dFlags & PackageManager.DELETE_KEEP_DATA) != 0); - GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName); + GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName()); try { - assertTrue(invokeDeletePackage(ip.pkg.packageName, dFlags, receiver)); + assertTrue(invokeDeletePackage(ip.pkg.getPackageName(), dFlags, receiver)); ApplicationInfo info = null; Log.i(TAG, "okay4"); try { - info = getPm().getApplicationInfo(ip.pkg.packageName, + info = getPm().getApplicationInfo(ip.pkg.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES); } catch (NameNotFoundException e) { info = null; } if (retainData) { assertNotNull(info); - assertEquals(info.packageName, ip.pkg.packageName); + assertEquals(info.packageName, ip.pkg.getPackageName()); } else { assertNull(info); } @@ -998,9 +1001,9 @@ public class PackageManagerTests extends AndroidTestCase { } Runtime.getRuntime().gc(); try { - cleanUpInstall(ip.pkg.packageName); + cleanUpInstall(ip.pkg.getPackageName()); } finally { - File outFile = new File(ip.pkg.codePath); + File outFile = new File(ip.pkg.getCodePath()); if (outFile != null && outFile.exists()) { outFile.delete(); } @@ -1070,13 +1073,13 @@ public class PackageManagerTests extends AndroidTestCase { InstallParams ip = installFromRawResource("install.apk", iApk, iFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName); + GenericReceiver receiver = new ReplaceReceiver(ip.pkg.getPackageName()); int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING; try { InstallParams rp = installFromRawResource("install.apk", rApk, replaceFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); - assertInstall(rp.pkg, replaceFlags, rp.pkg.installLocation); + assertInstall(rp.pkg, replaceFlags, rp.pkg.getInstallLocation()); } catch (Exception e) { failStr("Failed with exception : " + e); } finally { @@ -1103,7 +1106,7 @@ public class PackageManagerTests extends AndroidTestCase { InstallParams rp = installFromRawResource("install.apk", rApk, replaceFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); - assertInstall(rp.pkg, replaceFlags, ip.pkg.installLocation); + assertInstall(rp.pkg, replaceFlags, ip.pkg.getInstallLocation()); } catch (Exception e) { failStr("Failed with exception : " + e); } finally { @@ -1204,18 +1207,18 @@ public class PackageManagerTests extends AndroidTestCase { // Install first ip = installFromRawResource("install.apk", rawResId, installFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); - ApplicationInfo oldAppInfo = getPm().getApplicationInfo(ip.pkg.packageName, 0); + ApplicationInfo oldAppInfo = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0); if (fail) { - assertTrue(invokeMovePackageFail(ip.pkg.packageName, moveFlags, result)); - ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0); + assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result)); + ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0); assertNotNull(info); assertEquals(oldAppInfo.flags, info.flags); } else { // Create receiver based on expRetCode - MoveReceiver receiver = new MoveReceiver(ip.pkg.packageName); - boolean retCode = invokeMovePackage(ip.pkg.packageName, moveFlags, receiver); + MoveReceiver receiver = new MoveReceiver(ip.pkg.getPackageName()); + boolean retCode = invokeMovePackage(ip.pkg.getPackageName(), moveFlags, receiver); assertTrue(retCode); - ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0); + ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0); assertNotNull("ApplicationInfo for recently installed application should exist", info); if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) { @@ -1294,9 +1297,9 @@ public class PackageManagerTests extends AndroidTestCase { ip = installFromRawResource("install.apk", R.raw.install, installFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); // Delete the package now retaining data. - GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName); - invokeDeletePackage(ip.pkg.packageName, PackageManager.DELETE_KEEP_DATA, receiver); - assertTrue(invokeMovePackageFail(ip.pkg.packageName, moveFlags, result)); + GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName()); + invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver); + assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result)); } catch (Exception e) { failStr(e); } finally { @@ -1712,7 +1715,7 @@ public class PackageManagerTests extends AndroidTestCase { ip = installFromRawResource("install.apk", iApk, iFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip.pkg, iFlags, ip.pkg.installLocation); + assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_DEFINED); // **: Upon installing package, are its permissions granted? @@ -1722,15 +1725,15 @@ public class PackageManagerTests extends AndroidTestCase { ip2 = installFromRawResource("install2.apk", i2Apk, i2Flags, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation); + assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_USED); // **: Upon removing but not deleting, are permissions retained? - GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName); + GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName()); try { - invokeDeletePackage(ip.pkg.packageName, PackageManager.DELETE_KEEP_DATA, receiver); + invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver); } catch (Exception e) { failStr(e); } @@ -1742,14 +1745,14 @@ public class PackageManagerTests extends AndroidTestCase { ip = installFromRawResource("install.apk", iApk, iFlags | PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip.pkg, iFlags, ip.pkg.installLocation); + assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_DEFINED); assertPermissions(BASE_PERMISSIONS_USED); // **: Upon deleting package, are all permissions removed? try { - invokeDeletePackage(ip.pkg.packageName, 0, receiver); + invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver); ip = null; } catch (Exception e) { failStr(e); @@ -1759,9 +1762,9 @@ public class PackageManagerTests extends AndroidTestCase { // **: Delete package using permissions; nothing to check here. - GenericReceiver receiver2 = new DeleteReceiver(ip2.pkg.packageName); + GenericReceiver receiver2 = new DeleteReceiver(ip2.pkg.getPackageName()); try { - invokeDeletePackage(ip2.pkg.packageName, 0, receiver); + invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver); ip2 = null; } catch (Exception e) { failStr(e); @@ -1772,7 +1775,7 @@ public class PackageManagerTests extends AndroidTestCase { ip2 = installFromRawResource("install2.apk", i2Apk, i2Flags, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation); + assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_NOTUSED); // **: Upon installing declaring package, are sig permissions granted @@ -1781,7 +1784,7 @@ public class PackageManagerTests extends AndroidTestCase { ip = installFromRawResource("install.apk", iApk, iFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip.pkg, iFlags, ip.pkg.installLocation); + assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_DEFINED); assertPermissions(BASE_PERMISSIONS_SIGUSED); @@ -1790,13 +1793,13 @@ public class PackageManagerTests extends AndroidTestCase { ip2 = installFromRawResource("install2.apk", i2Apk, i2Flags | PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation); + assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation()); assertPermissions(BASE_PERMISSIONS_NOTUSED); // **: Upon deleting package, are all permissions removed? try { - invokeDeletePackage(ip.pkg.packageName, 0, receiver); + invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver); ip = null; } catch (Exception e) { failStr(e); @@ -1807,7 +1810,7 @@ public class PackageManagerTests extends AndroidTestCase { // **: Delete package using permissions; nothing to check here. try { - invokeDeletePackage(ip2.pkg.packageName, 0, receiver); + invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver); ip2 = null; } catch (Exception e) { failStr(e); @@ -1862,7 +1865,7 @@ public class PackageManagerTests extends AndroidTestCase { int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; String apk1Name = "install1.apk"; String apk2Name = "install2.apk"; - PackageParser.Package pkg1 = getParsedPackage(apk1Name, apk1); + ParsingPackage pkg1 = getParsedPackage(apk1Name, apk1); try { InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); @@ -1873,7 +1876,7 @@ public class PackageManagerTests extends AndroidTestCase { failStr(e.getMessage()); } finally { if (cleanUp) { - cleanUpInstall(pkg1.packageName); + cleanUpInstall(pkg1.getPackageName()); } } return null; @@ -2460,16 +2463,16 @@ public class PackageManagerTests extends AndroidTestCase { File outFile = new File(filesDir, apk2Name); int rawResId = apk2; Uri packageURI = getInstallablePackage(rawResId, outFile); - PackageParser.Package pkg = parsePackage(packageURI); + ParsingPackage pkg = parsePackage(packageURI); try { - getPi().uninstall(pkg.packageName, + getPi().uninstall(pkg.getPackageName(), PackageManager.DELETE_ALL_USERS, null /*statusReceiver*/); } catch (IllegalArgumentException ignore) { } // Check signatures now int match = mContext.getPackageManager().checkSignatures( - ip.pkg.packageName, pkg.packageName); + ip.pkg.getPackageName(), pkg.getPackageName()); assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match); } finally { cleanUpInstall(ip); @@ -2530,13 +2533,13 @@ public class PackageManagerTests extends AndroidTestCase { int retCode, int expMatchResult) throws Exception { String apk1Name = "install1.apk"; String apk2Name = "install2.apk"; - PackageParser.Package pkg1 = getParsedPackage(apk1Name, apk1); - PackageParser.Package pkg2 = getParsedPackage(apk2Name, apk2); + ParsingPackage pkg1 = getParsedPackage(apk1Name, apk1); + ParsingPackage pkg2 = getParsedPackage(apk2Name, apk2); try { // Clean up before testing first. - cleanUpInstall(pkg1.packageName); - cleanUpInstall(pkg2.packageName); + cleanUpInstall(pkg1.getPackageName()); + cleanUpInstall(pkg2.getPackageName()); installFromRawResource(apk1Name, apk1, 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); if (fail) { @@ -2545,14 +2548,14 @@ public class PackageManagerTests extends AndroidTestCase { } else { installFromRawResource(apk2Name, apk2, 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); - int match = mContext.getPackageManager().checkSignatures(pkg1.packageName, - pkg2.packageName); + int match = mContext.getPackageManager().checkSignatures(pkg1.getPackageName(), + pkg2.getPackageName()); assertEquals(expMatchResult, match); } } finally { if (cleanUp) { - cleanUpInstall(pkg1.packageName); - cleanUpInstall(pkg2.packageName); + cleanUpInstall(pkg1.getPackageName()); + cleanUpInstall(pkg2.getPackageName()); } } } @@ -2618,15 +2621,15 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); PackageManager pm = mContext.getPackageManager(); // Delete app2 - PackageParser.Package pkg = getParsedPackage(apk2Name, apk2); + ParsingPackage pkg = getParsedPackage(apk2Name, apk2); try { - getPi().uninstall( - pkg.packageName, PackageManager.DELETE_ALL_USERS, null /*statusReceiver*/); + getPi().uninstall(pkg.getPackageName(), PackageManager.DELETE_ALL_USERS, + null /*statusReceiver*/); } catch (IllegalArgumentException ignore) { } // Check signatures now int match = mContext.getPackageManager().checkSignatures( - ip1.pkg.packageName, pkg.packageName); + ip1.pkg.getPackageName(), pkg.getPackageName()); assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match); } finally { if (ip1 != null) { @@ -2831,8 +2834,8 @@ public class PackageManagerTests extends AndroidTestCase { PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); try { // then, remove it, keeping it's data around - final GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName); - invokeDeletePackage(ip.pkg.packageName, PackageManager.DELETE_KEEP_DATA, receiver); + final GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName()); + invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver); final List<PackageInfo> packages = getPm().getInstalledPackages(flags); assertNotNull("installed packages cannot be null", packages); diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 164c372768c0..bfcf52af80bf 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -100,12 +100,12 @@ public class ImeInsetsSourceConsumerTest { // test if setVisibility can show IME mImeConsumer.onWindowFocusGained(); mImeConsumer.applyImeVisibility(true); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test if setVisibility can hide IME mImeConsumer.applyImeVisibility(false); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index cc85332590ba..d4c256972b28 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -245,14 +245,14 @@ public class InsetsControllerTest { mController.applyImeVisibility(true /* setVisible */); mController.show(Type.all()); // quickly jump to final state by cancelling it. - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false /* setVisible */); mController.hide(Type.all()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -268,10 +268,10 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(); mController.applyImeVisibility(true); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); @@ -291,7 +291,7 @@ public class InsetsControllerTest { mController.hide(types); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); @@ -302,7 +302,7 @@ public class InsetsControllerTest { mController.show(types); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -321,21 +321,21 @@ public class InsetsControllerTest { int types = Type.navigationBars() | Type.systemBars(); // test show select types. mController.show(types); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(Type.all()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single show mController.show(Type.navigationBars()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -363,7 +363,7 @@ public class InsetsControllerTest { mController.hide(Type.systemBars()); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -372,7 +372,7 @@ public class InsetsControllerTest { mController.show(Type.systemBars()); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -383,7 +383,7 @@ public class InsetsControllerTest { mController.hide(Type.navigationBars()); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -391,7 +391,7 @@ public class InsetsControllerTest { mController.hide(Type.systemBars()); assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -411,13 +411,13 @@ public class InsetsControllerTest { // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); @@ -431,7 +431,7 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.hide(Type.statusBars()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); @@ -446,7 +446,7 @@ public class InsetsControllerTest { // Gaining control mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); }); @@ -468,7 +468,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible()); assertTrue(mController.getState().getSource(ITYPE_IME).isVisible()); }); @@ -489,7 +489,7 @@ public class InsetsControllerTest { mController.show(ime(), true /* fromIme */); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME)); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible()); assertTrue(mController.getState().getSource(ITYPE_IME).isVisible()); }); @@ -658,7 +658,7 @@ public class InsetsControllerTest { mController.getState().getSource(ITYPE_IME).getFrame()); assertNotEquals(new Rect(4, 5, 6, 7), mController.getState().getSource(ITYPE_IME).getVisibleFrame()); - mController.cancelExistingAnimation(); + mController.cancelExistingAnimations(); assertEquals(new Rect(0, 1, 2, 3), mController.getState().getSource(ITYPE_IME).getFrame()); assertEquals(new Rect(4, 5, 6, 7), diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 80fb35813009..e23a3cad914b 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1763,7 +1763,8 @@ public class ChooserActivityTest { Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - Intent chooserIntent = createChooserIntent(new Intent[] {new Intent("action.fake")}); + Intent chooserIntent = createChooserIntent(createSendTextIntent(), + new Intent[] {new Intent("action.fake")}); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); @@ -1796,7 +1797,7 @@ public class ChooserActivityTest { new Intent("action.fake1"), new Intent("action.fake2") }; - Intent chooserIntent = createChooserIntent(initialIntents); + Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents); sOverrides.packageManager = mock(PackageManager.class); when(sOverrides.packageManager.resolveActivity(any(Intent.class), anyInt())) .thenReturn(createFakeResolveInfo()); @@ -1809,12 +1810,74 @@ public class ChooserActivityTest { assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0)); } - private Intent createChooserIntent(Intent[] initialIntents) { + @Test + public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + sOverrides.hasCrossProfileIntents = false; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent[] initialIntents = { + new Intent("action.fake1"), + new Intent("action.fake2") + }; + Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); + sOverrides.packageManager = mock(PackageManager.class); + when(sOverrides.packageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(createFakeResolveInfo()); + + final ChooserWrapperActivity activity = mActivityRule.launchActivity(chooserIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + + onView(withText(R.string.resolver_cant_access_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent[] initialIntents = { + new Intent("action.fake1"), + new Intent("action.fake2") + }; + Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); + sOverrides.packageManager = mock(PackageManager.class); + when(sOverrides.packageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(createFakeResolveInfo()); + + mActivityRule.launchActivity(chooserIntent); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_no_work_apps_available_resolve)) + .check(matches(isDisplayed())); + } + + private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { Intent chooserIntent = new Intent(); chooserIntent.setAction(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title"); - chooserIntent.putExtra(Intent.EXTRA_INTENT, createSendTextIntent()); + chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); chooserIntent.setType("text/plain"); if (initialIntents != null) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents); diff --git a/errorprone/Android.bp b/errorprone/Android.bp index 016b85510a94..098f4bfa74ac 100644 --- a/errorprone/Android.bp +++ b/errorprone/Android.bp @@ -20,6 +20,4 @@ java_library_host { plugins: [ "//external/dagger2:dagger2-auto-service", ], - - javacflags: ["-verbose"], } diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java new file mode 100644 index 000000000000..48123abd26cb --- /dev/null +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns.android; + +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Matchers.enclosingClass; +import static com.google.errorprone.matchers.Matchers.hasAnnotation; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.isSameType; +import static com.google.errorprone.matchers.Matchers.methodInvocation; +import static com.google.errorprone.matchers.Matchers.throwStatement; +import static com.google.errorprone.matchers.Matchers.variableType; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.CatchTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; + +import java.util.List; + +/** + * Apps making calls into the system server may end up persisting internal state + * or making security decisions based on the perceived success or failure of a + * call, or any default values returned. For this reason, we want to strongly + * throw when there was trouble with the transaction. + * <p> + * The rethrowFromSystemServer() method is the best-practice way of doing this + * correctly, so that we don't clutter logs with misleading stack traces, and + * this checker verifies that best-practice is used. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "AndroidFrameworkRethrowFromSystem", + summary = "Verifies that system_server calls use rethrowFromSystemServer()", + severity = WARNING) +public final class RethrowFromSystemChecker extends BugChecker implements CatchTreeMatcher { + private static final Matcher<Tree> INSIDE_MANAGER = + enclosingClass(hasAnnotation("android.annotation.SystemService")); + private static final Matcher<VariableTree> REMOTE_EXCEPTION = variableType( + isSameType("android.os.RemoteException")); + private static final Matcher<StatementTree> RETHROW_FROM_SYSTEM = throwStatement( + methodInvocation(instanceMethod().onExactClass("android.os.RemoteException") + .named("rethrowFromSystemServer"))); + + @Override + public Description matchCatch(CatchTree tree, VisitorState state) { + if (INSIDE_MANAGER.matches(tree, state) + && REMOTE_EXCEPTION.matches(tree.getParameter(), state)) { + final List<? extends StatementTree> statements = tree.getBlock().getStatements(); + if (statements.size() != 1 || !RETHROW_FROM_SYSTEM.matches(statements.get(0), state)) { + return buildDescription(tree) + .setMessage("Must contain single " + + "'throw e.rethrowFromSystemServer()' statement") + .build(); + } + } + return Description.NO_MATCH; + } +} diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java index 1ce816c34990..232cf3f0d677 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java @@ -34,6 +34,27 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree.Kind; +/** + * Over the years we've had several obscure bugs related to how SDK level + * comparisons are performed, specifically during the window of time where we've + * started distributing the "frankenbuild" to developers. + * <p> + * Consider the case where a framework developer shipping release "R" wants to + * only grant a specific behavior to modern apps; they could write this in two + * different ways: + * <ol> + * <li>if (targetSdkVersion > Build.VERSION_CODES.Q) { + * <li>if (targetSdkVersion >= Build.VERSION_CODES.R) { + * </ol> + * The safer of these two options is (2), which will ensure that developers only + * get the behavior when <em>both</em> the app and the platform agree on the + * specific SDK level having shipped. + * <p> + * Consider the breakage that would happen with option (1) if we started + * shipping APKs that are based on the final R SDK, but are then installed on + * earlier preview releases which still consider R to be CUR_DEVELOPMENT; they'd + * risk crashing due to behaviors that were never part of the official R SDK. + */ @AutoService(BugChecker.class) @BugPattern( name = "AndroidFrameworkTargetSdk", diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 412b43e6a582..a112bdd0ce03 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2954,10 +2954,10 @@ public class LocationManager { @Override protected void unregisterService() throws RemoteException { - Preconditions.checkState(mListenerTransport != null); - - mService.unregisterGnssStatusCallback(mListenerTransport); - mListenerTransport = null; + if (mListenerTransport != null) { + mService.unregisterGnssStatusCallback(mListenerTransport); + mListenerTransport = null; + } } private class GnssStatusListener extends IGnssStatusListener.Stub { @@ -3020,10 +3020,10 @@ public class LocationManager { @Override protected void unregisterService() throws RemoteException { - Preconditions.checkState(mListenerTransport != null); - - mService.removeGnssMeasurementsListener(mListenerTransport); - mListenerTransport = null; + if (mListenerTransport != null) { + mService.removeGnssMeasurementsListener(mListenerTransport); + mListenerTransport = null; + } } @Override @@ -3073,10 +3073,10 @@ public class LocationManager { @Override protected void unregisterService() throws RemoteException { - Preconditions.checkState(mListenerTransport != null); - - mService.removeGnssNavigationMessageListener(mListenerTransport); - mListenerTransport = null; + if (mListenerTransport != null) { + mService.removeGnssNavigationMessageListener(mListenerTransport); + mListenerTransport = null; + } } private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub { @@ -3114,10 +3114,10 @@ public class LocationManager { @Override protected void unregisterService() throws RemoteException { - Preconditions.checkState(mListenerTransport != null); - - mService.removeGnssAntennaInfoListener(mListenerTransport); - mListenerTransport = null; + if (mListenerTransport != null) { + mService.removeGnssAntennaInfoListener(mListenerTransport); + mListenerTransport = null; + } } private class GnssAntennaInfoListener extends IGnssAntennaInfoListener.Stub { @@ -3151,10 +3151,10 @@ public class LocationManager { @Override protected void unregisterService() throws RemoteException { - Preconditions.checkState(mListenerTransport != null); - - mService.removeGnssBatchingCallback(); - mListenerTransport = null; + if (mListenerTransport != null) { + mService.removeGnssBatchingCallback(); + mListenerTransport = null; + } } private class BatchedLocationCallback extends IBatchedLocationCallback.Stub { diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java index c91ff0d099cf..ff9fd4187272 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -166,10 +166,25 @@ public final class AudioMetadata { * * A Boolean value which is true if Atmos is present in an E-AC3 stream. */ + + // Since Boolean isn't handled by Parceling, we translate + // internally to KEY_HAS_ATMOS when sending through JNI. + // Consider deprecating this key for KEY_HAS_ATMOS in the future. + // @NonNull public static final Key<Boolean> KEY_ATMOS_PRESENT = createKey("atmos-present", Boolean.class); /** + * A key representing the presence of Atmos in an E-AC3 stream. + * + * An Integer value which is nonzero if Atmos is present in an E-AC3 stream. + * The integer representation is used for communication to the native side. + * @hide + */ + @NonNull public static final Key<Integer> KEY_HAS_ATMOS = + createKey("has-atmos", Integer.class); + + /** * A key representing the audio encoding used for the stream. * This is the same encoding used in {@link AudioFormat#getEncoding()}. * @@ -731,6 +746,15 @@ public final class AudioMetadata { Log.e(TAG, "Failed to unpack value for map"); return null; } + + // Special handling of KEY_ATMOS_PRESENT. + if (key.equals(Format.KEY_HAS_ATMOS.getName()) + && value.first == Format.KEY_HAS_ATMOS.getValueClass()) { + ret.set(Format.KEY_ATMOS_PRESENT, + (Boolean) ((int) value.second != 0)); // Translate Integer to Boolean + continue; // Should we store both keys in the java table? + } + ret.set(createKey(key, value.first), value.first.cast(value.second)); } return ret; @@ -746,11 +770,19 @@ public final class AudioMetadata { return false; } for (Key<?> key : obj.keySet()) { + Object value = obj.get(key); + + // Special handling of KEY_ATMOS_PRESENT. + if (key == Format.KEY_ATMOS_PRESENT) { + key = Format.KEY_HAS_ATMOS; + value = (Integer) ((boolean) value ? 1 : 0); // Translate Boolean to Integer + } + if (!strDataPackage.pack(output, key.getName())) { Log.i(TAG, "Failed to pack key: " + key.getName()); return false; } - if (!OBJECT_PACKAGE.pack(output, new Pair<>(key.getValueClass(), obj.get(key)))) { + if (!OBJECT_PACKAGE.pack(output, new Pair<>(key.getValueClass(), value))) { Log.i(TAG, "Failed to pack value: " + obj.get(key)); return false; } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5d61dd06c792..e3c7336905d1 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -24,7 +24,6 @@ import android.annotation.Nullable; import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSessionManager; -import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -171,8 +170,7 @@ public final class MediaRouter2Manager { public MediaController getMediaControllerForRoutingSession( @NonNull RoutingSessionInfo sessionInfo) { for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { - String volumeControlId = controller.getPlaybackInfo().getVolumeControlId(); - if (TextUtils.equals(sessionInfo.getId(), volumeControlId)) { + if (areSessionsMatched(controller, sessionInfo)) { return controller; } } @@ -207,6 +205,37 @@ public final class MediaRouter2Manager { } /** + * Gets available routes for the given routing session. + * The returned routes can be passed to + * {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} for transferring the routing session. + * + * @param sessionInfo the routing session that would be transferred + */ + @NonNull + public List<MediaRoute2Info> getAvailableRoutesForRoutingSession( + @NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + List<MediaRoute2Info> routes = new ArrayList<>(); + + String packageName = sessionInfo.getClientPackageName(); + List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); + if (preferredFeatures == null) { + preferredFeatures = Collections.emptyList(); + } + synchronized (mRoutesLock) { + for (MediaRoute2Info route : mRoutes.values()) { + if (route.isSystemRoute() || route.hasAnyFeatures(preferredFeatures) + || sessionInfo.getSelectedRoutes().contains(route.getId()) + || sessionInfo.getTransferableRoutes().contains(route.getId())) { + routes.add(route); + } + } + } + return routes; + } + + /** * Gets the system routing session associated with no specific application. */ @NonNull @@ -220,6 +249,33 @@ public final class MediaRouter2Manager { } /** + * Gets the routing session of a media session. + * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_LOCAL local playback}, + * the system routing session is returned. + * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_REMOTE remote playback}, + * it returns the corresponding routing session or {@code null} if it's unavailable. + */ + @Nullable + public RoutingSessionInfo getRoutingSessionForMediaController(MediaController mediaController) { + MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo(); + if (playbackInfo == null) { + return null; + } + if (playbackInfo.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) { + return new RoutingSessionInfo.Builder(getSystemRoutingSession()) + .setClientPackageName(mediaController.getPackageName()) + .build(); + } + for (RoutingSessionInfo sessionInfo : getActiveSessions()) { + if (!sessionInfo.isSystemSession() + && areSessionsMatched(mediaController, sessionInfo)) { + return sessionInfo; + } + } + return null; + } + + /** * Gets routing sessions of an application with the given package name. * The first element of the returned list is the system routing session. * @@ -344,14 +400,6 @@ public final class MediaRouter2Manager { /** * Requests a volume change for a route asynchronously. - */ - //TODO: remove this. - public void requestSetVolume(MediaRoute2Info route, int volume) { - setRouteVolume(route, volume); - } - - /** - * Requests a volume change for a route asynchronously. * <p> * It may have no effect if the route is currently not selected. * </p> @@ -576,22 +624,11 @@ public final class MediaRouter2Manager { } for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute(() -> record.mCallback - .onControlCategoriesChanged(packageName, preferredFeatures)); - } - for (CallbackRecord record : mCallbackRecords) { - record.mExecutor.execute(() -> record.mCallback .onPreferredFeaturesChanged(packageName, preferredFeatures)); } } /** - * @hide - */ - public RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) { - return new RoutingController(sessionInfo); - } - - /** * Gets the unmodifiable list of selected routes for the session. */ @NonNull @@ -782,153 +819,32 @@ public final class MediaRouter2Manager { } } - private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) { - synchronized (sLock) { - return routeIds.stream().map(mRoutes::get) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + private boolean areSessionsMatched(MediaController mediaController, + RoutingSessionInfo sessionInfo) { + MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo(); + if (playbackInfo == null) { + return false; } - } - //TODO: Remove this. - /** - * A class to control media routing session in media route provider. - * With routing controller, an application can select a route into the session or deselect - * a route in the session. - */ - public final class RoutingController { - private final Object mControllerLock = new Object(); - @GuardedBy("mControllerLock") - private RoutingSessionInfo mSessionInfo; - - RoutingController(@NonNull RoutingSessionInfo sessionInfo) { - mSessionInfo = sessionInfo; + String volumeControlId = playbackInfo.getVolumeControlId(); + if (volumeControlId == null) { + return false; } - /** - * Releases the session - */ - public void release() { - synchronized (mControllerLock) { - releaseSession(mSessionInfo); - } - } - - /** - * Gets the ID of the session - */ - @NonNull - public String getSessionId() { - synchronized (mControllerLock) { - return mSessionInfo.getId(); - } - } - - /** - * Gets the client package name of the session - */ - @NonNull - public String getClientPackageName() { - synchronized (mControllerLock) { - return mSessionInfo.getClientPackageName(); - } - } - - /** - * @return the control hints used to control route session if available. - */ - @Nullable - public Bundle getControlHints() { - synchronized (mControllerLock) { - return mSessionInfo.getControlHints(); - } - } - - /** - * @return the unmodifiable list of currently selected routes - */ - @NonNull - public List<MediaRoute2Info> getSelectedRoutes() { - return MediaRouter2Manager.this.getSelectedRoutes(mSessionInfo); - } - - /** - * @return the unmodifiable list of selectable routes for the session. - */ - @NonNull - public List<MediaRoute2Info> getSelectableRoutes() { - return MediaRouter2Manager.this.getSelectableRoutes(mSessionInfo); - } - - /** - * @return the unmodifiable list of deselectable routes for the session. - */ - @NonNull - public List<MediaRoute2Info> getDeselectableRoutes() { - return MediaRouter2Manager.this.getDeselectableRoutes(mSessionInfo); - } - - /** - * @return the unmodifiable list of transferable routes for the session. - */ - @NonNull - public List<MediaRoute2Info> getTransferableRoutes() { - List<String> routeIds; - synchronized (mControllerLock) { - routeIds = mSessionInfo.getTransferableRoutes(); - } - return getRoutesWithIds(routeIds); - } - - /** - * Selects a route for the remote session. The given route must satisfy all of the - * following conditions: - * <ul> - * <li>ID should not be included in {@link #getSelectedRoutes()}</li> - * <li>ID should be included in {@link #getSelectableRoutes()}</li> - * </ul> - * If the route doesn't meet any of above conditions, it will be ignored. - * - * @see #getSelectedRoutes() - * @see #getSelectableRoutes() - */ - public void selectRoute(@NonNull MediaRoute2Info route) { - MediaRouter2Manager.this.selectRoute(mSessionInfo, route); - } - - /** - * Deselects a route from the remote session. The given route must satisfy all of the - * following conditions: - * <ul> - * <li>ID should be included in {@link #getSelectedRoutes()}</li> - * <li>ID should be included in {@link #getDeselectableRoutes()}</li> - * </ul> - * If the route doesn't meet any of above conditions, it will be ignored. - * - * @see #getSelectedRoutes() - * @see #getDeselectableRoutes() - */ - public void deselectRoute(@NonNull MediaRoute2Info route) { - MediaRouter2Manager.this.deselectRoute(mSessionInfo, route); - } - - /** - * Transfers session to the given rotue. - */ - public void transferToRoute(@NonNull MediaRoute2Info route) { - MediaRouter2Manager.this.transferToRoute(mSessionInfo, route); + if (TextUtils.equals(volumeControlId, sessionInfo.getId())) { + return true; } + // Workaround for provider not being able to know the unique session ID. + return TextUtils.equals(volumeControlId, sessionInfo.getOriginalId()) + && TextUtils.equals(mediaController.getPackageName(), + sessionInfo.getOwnerPackageName()); + } - /** - * Gets the session info of the session - * - * @hide - */ - @NonNull - public RoutingSessionInfo getSessionInfo() { - synchronized (mControllerLock) { - return mSessionInfo; - } + private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) { + synchronized (sLock) { + return routeIds.stream().map(mRoutes::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } } @@ -976,16 +892,6 @@ public final class MediaRouter2Manager { public void onTransferFailed(@NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) { } - //TODO: Remove this. - /** - * Called when the preferred route features of an app is changed. - * - * @param packageName the package name of the application - * @param preferredFeatures the list of preferred route features set by an application. - */ - public void onControlCategoriesChanged(@NonNull String packageName, - @NonNull List<String> preferredFeatures) {} - /** * Called when the preferred route features of an app is changed. * diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 608e29a7a6ca..edf1fc58ecf5 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -50,6 +50,7 @@ public final class RoutingSessionInfo implements Parcelable { final String mId; final CharSequence mName; + final String mOwnerPackageName; final String mClientPackageName; @Nullable final String mProviderId; @@ -71,6 +72,7 @@ public final class RoutingSessionInfo implements Parcelable { mId = builder.mId; mName = builder.mName; + mOwnerPackageName = builder.mOwnerPackageName; mClientPackageName = builder.mClientPackageName; mProviderId = builder.mProviderId; @@ -96,6 +98,7 @@ public final class RoutingSessionInfo implements Parcelable { mId = ensureString(src.readString()); mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); + mOwnerPackageName = src.readString(); mClientPackageName = ensureString(src.readString()); mProviderId = src.readString(); @@ -159,6 +162,15 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * Gets the package name of the session owner. + * @hide + */ + @Nullable + public String getOwnerPackageName() { + return mOwnerPackageName; + } + + /** * Gets the client package name of the session */ @NonNull @@ -263,6 +275,7 @@ public final class RoutingSessionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mId); dest.writeCharSequence(mName); + dest.writeString(mOwnerPackageName); dest.writeString(mClientPackageName); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); @@ -288,6 +301,7 @@ public final class RoutingSessionInfo implements Parcelable { RoutingSessionInfo other = (RoutingSessionInfo) obj; return Objects.equals(mId, other.mId) && Objects.equals(mName, other.mName) + && Objects.equals(mOwnerPackageName, other.mOwnerPackageName) && Objects.equals(mClientPackageName, other.mClientPackageName) && Objects.equals(mProviderId, other.mProviderId) && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) @@ -301,7 +315,7 @@ public final class RoutingSessionInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mName, mClientPackageName, mProviderId, + return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId, mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes, mVolumeMax, mVolumeHandling, mVolume); } @@ -356,6 +370,7 @@ public final class RoutingSessionInfo implements Parcelable { // TODO: Reorder these (important ones first) final String mId; CharSequence mName; + String mOwnerPackageName; String mClientPackageName; String mProviderId; final List<String> mSelectedRoutes; @@ -440,6 +455,17 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * Sets the package name of the session owner. It is expected to be called by the system. + * + * @hide + */ + @NonNull + public Builder setOwnerPackageName(@Nullable String packageName) { + mOwnerPackageName = packageName; + return this; + } + + /** * Sets the client package name of the session. * * @hide diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index f058a02ceb1e..e701055c2894 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1805,7 +1805,7 @@ public final class TvInputManager { String tvInputSessionId, int priorityHint, Executor executor, final HardwareCallback callback) { try { - return new Hardware( + ITvInputHardware hardware = mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() { @Override public void onReleased() { @@ -1826,7 +1826,11 @@ public final class TvInputManager { Binder.restoreCallingIdentity(identity); } } - }, info, mUserId, tvInputSessionId, priorityHint)); + }, info, mUserId, tvInputSessionId, priorityHint); + if (hardware == null) { + return null; + } + return new Hardware(hardware); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java b/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java index 598ff8f3f075..28f1ac916690 100644 --- a/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java +++ b/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java @@ -17,6 +17,7 @@ package android.media.tv.tunerresourcemanager; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -81,7 +82,7 @@ public final class ResourceClientProfile implements Parcelable { * OEM. The id of the useCaseVendor should be passed through this parameter. Any * undefined use case would cause IllegalArgumentException. */ - public ResourceClientProfile(@NonNull String tvInputSessionId, + public ResourceClientProfile(@Nullable String tvInputSessionId, int useCase) { mTvInputSessionId = tvInputSessionId; mUseCase = useCase; @@ -92,7 +93,7 @@ public final class ResourceClientProfile implements Parcelable { * * @return the value of the tv input session id. */ - @NonNull + @Nullable public String getTvInputSessionId() { return mTvInputSessionId; } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java index 9e194fb49d3a..288e5cf13c2e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java @@ -73,7 +73,7 @@ public class CarNavigationBarController { } /** - * Hides all navigation bars. + * Hides all system bars. */ public void hideBars() { if (mTopView != null) { @@ -85,7 +85,7 @@ public class CarNavigationBarController { } /** - * Shows all navigation bars. + * Shows all system bars. */ public void showBars() { if (mTopView != null) { diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java index 20fcca0d0220..aeb1d39599db 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java @@ -29,41 +29,40 @@ import android.widget.FrameLayout; import com.android.car.notification.R; import com.android.car.notification.headsup.CarHeadsUpNotificationContainer; import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.car.window.OverlayViewGlobalStateController; import com.android.systemui.dagger.qualifiers.Main; import javax.inject.Inject; import javax.inject.Singleton; -import dagger.Lazy; - /** * A controller for SysUI's HUN display. */ @Singleton public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer { private final CarDeviceProvisionedController mCarDeviceProvisionedController; - private final Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy; + private final OverlayViewGlobalStateController mOverlayViewGlobalStateController; private final ViewGroup mWindow; private final FrameLayout mHeadsUpContentFrame; - private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen; - @Inject CarHeadsUpNotificationSystemContainer(Context context, @Main Resources resources, CarDeviceProvisionedController deviceProvisionedController, WindowManager windowManager, - Lazy<NotificationPanelViewController> notificationPanelViewControllerLazy) { + OverlayViewGlobalStateController overlayViewGlobalStateController) { mCarDeviceProvisionedController = deviceProvisionedController; - mNotificationPanelViewControllerLazy = notificationPanelViewControllerLazy; + mOverlayViewGlobalStateController = overlayViewGlobalStateController; boolean showOnBottom = resources.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom); + // Use TYPE_STATUS_BAR_SUB_PANEL window type since we need to find a window that is above + // status bar but below navigation bar. WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT); @@ -78,15 +77,11 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica windowManager.addView(mWindow, lp); mWindow.setVisibility(View.INVISIBLE); mHeadsUpContentFrame = mWindow.findViewById(R.id.headsup_content); - - mEnableHeadsUpNotificationWhenNotificationShadeOpen = resources.getBoolean( - R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen); } private void animateShow() { - if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen - || !mNotificationPanelViewControllerLazy.get().isPanelExpanded()) - && mCarDeviceProvisionedController.isCurrentUserFullySetup()) { + if (mCarDeviceProvisionedController.isCurrentUserFullySetup() + && mOverlayViewGlobalStateController.shouldShowHUN()) { mWindow.setVisibility(View.VISIBLE); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java index cb9539ad5b1d..1738091d14c9 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -73,6 +73,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController private final CarNotificationListener mCarNotificationListener; private final NotificationClickHandlerFactory mNotificationClickHandlerFactory; private final StatusBarStateController mStatusBarStateController; + private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen; private float mInitialBackgroundAlpha; private float mBackgroundAlphaDiff; @@ -144,6 +145,10 @@ public class NotificationPanelViewController extends OverlayPanelViewController + " percentage"); } mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha; + + mEnableHeadsUpNotificationWhenNotificationShadeOpen = mResources.getBoolean( + com.android.car.notification.R.bool + .config_enableHeadsUpNotificationWhenNotificationShadeOpen); } @Override @@ -151,6 +156,16 @@ public class NotificationPanelViewController extends OverlayPanelViewController reinflate(); } + @Override + protected boolean shouldShowNavigationBar() { + return true; + } + + @Override + protected boolean shouldShowHUN() { + return mEnableHeadsUpNotificationWhenNotificationShadeOpen; + } + /** Reinflates the view. */ public void reinflate() { ViewGroup container = (ViewGroup) getLayout(); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java index 8f52638afdf1..41349b284147 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java @@ -26,8 +26,15 @@ import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.car.window.OverlayViewMediator; import com.android.systemui.statusbar.policy.ConfigurationController; -/** The view mediator which attaches the view controller to other elements of the system ui. */ -public abstract class NotificationPanelViewMediator implements OverlayViewMediator, +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * The view mediator which attaches the view controller to other elements of the system ui. Disables + * drag open behavior of the notification panel from any navigation bar. + */ +@Singleton +public class NotificationPanelViewMediator implements OverlayViewMediator, ConfigurationController.ConfigurationListener { private final CarNavigationBarController mCarNavigationBarController; @@ -36,6 +43,7 @@ public abstract class NotificationPanelViewMediator implements OverlayViewMediat private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final ConfigurationController mConfigurationController; + @Inject public NotificationPanelViewMediator( CarNavigationBarController carNavigationBarController, NotificationPanelViewController notificationPanelViewController, diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java index 0fe985684543..45808a8a0b3e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java @@ -375,10 +375,10 @@ public abstract class OverlayPanelViewController extends OverlayViewController { } if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) { - getOverlayViewGlobalStateController().setWindowVisible(true); + getOverlayViewGlobalStateController().showView(/* panelViewController= */ this); } if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) { - getOverlayViewGlobalStateController().setWindowVisible(false); + getOverlayViewGlobalStateController().hideView(/* panelViewController= */ this); } getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE); getOverlayViewGlobalStateController().setWindowFocusable(visible); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java index 87f20208476b..30e26578bd73 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java @@ -54,7 +54,6 @@ public class OverlayViewController { mOverlayViewGlobalStateController.hideView(/* viewController= */ this, this::hide); } - /** * Inflate layout owned by controller. */ @@ -72,7 +71,7 @@ public class OverlayViewController { } /** - * Returns [@code true} if layout owned by controller has been inflated. + * Returns {@code true} if layout owned by controller has been inflated. */ public final boolean isInflated() { return mLayout != null; @@ -125,4 +124,18 @@ public class OverlayViewController { protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() { return mOverlayViewGlobalStateController; } + + /** + * Returns {@code true} if heads up notifications should be displayed over this view. + */ + protected boolean shouldShowHUN() { + return true; + } + + /** + * Returns {@code true} if navigation bar should be displayed over this view. + */ + protected boolean shouldShowNavigationBar() { + return false; + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java index 290505f5042a..70260b0d4cef 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java @@ -16,14 +16,17 @@ package com.android.systemui.car.window; +import android.annotation.Nullable; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.systemui.car.navigationbar.CarNavigationBarController; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; import javax.inject.Inject; import javax.inject.Singleton; @@ -39,11 +42,17 @@ import javax.inject.Singleton; */ @Singleton public class OverlayViewGlobalStateController { + private static final boolean DEBUG = false; private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName(); + private static final int UNKNOWN_Z_ORDER = -1; private final SystemUIOverlayWindowController mSystemUIOverlayWindowController; private final CarNavigationBarController mCarNavigationBarController; @VisibleForTesting - Set<String> mShownSet; + Map<OverlayViewController, Integer> mZOrderMap; + @VisibleForTesting + SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap; + @VisibleForTesting + OverlayViewController mHighestZOrder; @Inject public OverlayViewGlobalStateController( @@ -52,7 +61,8 @@ public class OverlayViewGlobalStateController { mSystemUIOverlayWindowController = systemUIOverlayWindowController; mSystemUIOverlayWindowController.attach(); mCarNavigationBarController = carNavigationBarController; - mShownSet = new HashSet<>(); + mZOrderMap = new HashMap<>(); + mZOrderVisibleSortedMap = new TreeMap<>(); } /** @@ -66,51 +76,127 @@ public class OverlayViewGlobalStateController { } /** - * Show content in Overlay Window. + * Show content in Overlay Window using {@link OverlayPanelViewController}. + * + * This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)} + * where the runnable is nullified since the actual showing of the panel is handled by the + * controller itself. */ - public void showView(OverlayViewController viewController, Runnable show) { - if (mShownSet.isEmpty()) { - mCarNavigationBarController.hideBars(); + public void showView(OverlayPanelViewController panelViewController) { + showView(panelViewController, /* show= */ null); + } + + /** + * Show content in Overlay Window using {@link OverlayViewController}. + */ + public void showView(OverlayViewController viewController, @Nullable Runnable show) { + debugLog(); + if (mZOrderVisibleSortedMap.isEmpty()) { setWindowVisible(true); } + if (!(viewController instanceof OverlayPanelViewController)) { + inflateView(viewController); + } - inflateView(viewController); + if (show != null) { + show.run(); + } - show.run(); - mShownSet.add(viewController.getClass().getName()); + updateInternalsWhenShowingView(viewController); + refreshNavigationBarVisibility(); Log.d(TAG, "Content shown: " + viewController.getClass().getName()); + debugLog(); + } + + private void updateInternalsWhenShowingView(OverlayViewController viewController) { + int zOrder; + if (mZOrderMap.containsKey(viewController)) { + zOrder = mZOrderMap.get(viewController); + } else { + zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild( + viewController.getLayout()); + mZOrderMap.put(viewController, zOrder); + } + + mZOrderVisibleSortedMap.put(zOrder, viewController); + + refreshHighestZOrderWhenShowingView(viewController); + } + + private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) { + if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get( + viewController)) { + mHighestZOrder = viewController; + } + } + + /** + * Hide content in Overlay Window using {@link OverlayPanelViewController}. + * + * This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)} + * where the runnable is nullified since the actual hiding of the panel is handled by the + * controller itself. + */ + public void hideView(OverlayPanelViewController panelViewController) { + hideView(panelViewController, /* hide= */ null); } /** - * Hide content in Overlay Window. + * Hide content in Overlay Window using {@link OverlayViewController}. */ - public void hideView(OverlayViewController viewController, Runnable hide) { + public void hideView(OverlayViewController viewController, @Nullable Runnable hide) { + debugLog(); if (!viewController.isInflated()) { Log.d(TAG, "Content cannot be hidden since it isn't inflated: " + viewController.getClass().getName()); return; } - if (!mShownSet.contains(viewController.getClass().getName())) { - Log.d(TAG, "Content cannot be hidden since it isn't shown: " + if (!mZOrderMap.containsKey(viewController)) { + Log.d(TAG, "Content cannot be hidden since it has never been shown: " + + viewController.getClass().getName()); + return; + } + if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) { + Log.d(TAG, "Content cannot be hidden since it isn't currently shown: " + viewController.getClass().getName()); return; } - hide.run(); - mShownSet.remove(viewController.getClass().getName()); + if (hide != null) { + hide.run(); + } - if (mShownSet.isEmpty()) { - mCarNavigationBarController.showBars(); + mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController)); + refreshHighestZOrderWhenHidingView(viewController); + refreshNavigationBarVisibility(); + + if (mZOrderVisibleSortedMap.isEmpty()) { setWindowVisible(false); } Log.d(TAG, "Content hidden: " + viewController.getClass().getName()); + debugLog(); + } + + private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) { + if (mZOrderVisibleSortedMap.isEmpty()) { + mHighestZOrder = null; + return; + } + if (!mHighestZOrder.equals(viewController)) { + return; + } + + mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey()); } - /** Sets the window visibility state. */ - public void setWindowVisible(boolean expanded) { - mSystemUIOverlayWindowController.setWindowVisible(expanded); + private void refreshNavigationBarVisibility() { + if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) { + mCarNavigationBarController.showBars(); + } else { + mCarNavigationBarController.hideBars(); + } } /** Returns {@code true} is the window is visible. */ @@ -118,13 +204,14 @@ public class OverlayViewGlobalStateController { return mSystemUIOverlayWindowController.isWindowVisible(); } - /** Sets the focusable flag of the sysui overlawy window. */ - public void setWindowFocusable(boolean focusable) { - mSystemUIOverlayWindowController.setWindowFocusable(focusable); + private void setWindowVisible(boolean visible) { + mSystemUIOverlayWindowController.setWindowVisible(visible); } - /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the - * sysui overlay window */ + /** + * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the + * sysui overlay window. + */ public void setWindowNeedsInput(boolean needsInput) { mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput); } @@ -134,10 +221,34 @@ public class OverlayViewGlobalStateController { return mSystemUIOverlayWindowController.isWindowFocusable(); } + /** Sets the focusable flag of the sysui overlawy window. */ + public void setWindowFocusable(boolean focusable) { + mSystemUIOverlayWindowController.setWindowFocusable(focusable); + } + /** Inflates the view controlled by the given view controller. */ public void inflateView(OverlayViewController viewController) { if (!viewController.isInflated()) { viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout()); } } + + /** + * Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it. + */ + public boolean shouldShowHUN() { + return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN(); + } + + private void debugLog() { + if (!DEBUG) { + return; + } + + Log.d(TAG, "mHighestZOrder: " + mHighestZOrder); + Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size()); + Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap); + Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size()); + Log.d(TAG, "mZOrderMap: " + mZOrderMap); + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java index e1918ceeaea4..484aa63e8bda 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayWindowModule.java @@ -18,6 +18,7 @@ package com.android.systemui.car.window; import com.android.systemui.car.keyguard.CarKeyguardViewMediator; import com.android.systemui.car.notification.BottomNotificationPanelViewMediator; +import com.android.systemui.car.notification.NotificationPanelViewMediator; import com.android.systemui.car.notification.TopNotificationPanelViewMediator; import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator; @@ -32,6 +33,13 @@ import dagger.multibindings.IntoMap; @Module public abstract class OverlayWindowModule { + /** Injects NotificationPanelViewMediator. */ + @Binds + @IntoMap + @ClassKey(NotificationPanelViewMediator.class) + public abstract OverlayViewMediator bindNotificationPanelViewMediator( + NotificationPanelViewMediator notificationPanelViewMediator); + /** Injects TopNotificationPanelViewMediator. */ @Binds @IntoMap diff --git a/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml new file mode 100644 index 000000000000..03fe0e4fcf2e --- /dev/null +++ b/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Fullscreen views in sysui should be listed here in increasing Z order. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:background="@android:color/transparent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ViewStub android:id="@+id/overlay_view_controller_stub_1" + android:inflatedId="@+id/overlay_view_controller_1" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout="@layout/overlay_view_controller_stub"/> + + <ViewStub android:id="@+id/overlay_view_controller_stub_2" + android:inflatedId="@+id/overlay_view_controller_2" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout="@layout/overlay_view_controller_stub"/> + + <ViewStub android:id="@+id/overlay_view_controller_stub_3" + android:inflatedId="@+id/overlay_view_controller_3" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout="@layout/overlay_view_controller_stub"/> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java index a2192af14758..1b4621f1c279 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java @@ -16,9 +16,10 @@ package com.android.systemui.car.keyguard; -import static com.google.common.truth.Truth.assertThat; - +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -29,7 +30,6 @@ import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import com.android.internal.widget.LockPatternUtils; @@ -40,7 +40,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarServiceProvider; import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.car.window.OverlayViewGlobalStateController; -import com.android.systemui.car.window.SystemUIOverlayWindowController; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; @@ -51,6 +50,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -61,28 +61,20 @@ import dagger.Lazy; public class CarKeyguardViewControllerTest extends SysuiTestCase { private TestableCarKeyguardViewController mCarKeyguardViewController; - private OverlayViewGlobalStateController mOverlayViewGlobalStateController; - private ViewGroup mBaseLayout; @Mock + private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + @Mock private KeyguardBouncer mBouncer; @Mock private CarNavigationBarController mCarNavigationBarController; @Mock - private SystemUIOverlayWindowController mSystemUIOverlayWindowController; - @Mock private CarKeyguardViewController.OnKeyguardCancelClickedListener mCancelClickedListener; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mOverlayViewGlobalStateController = new OverlayViewGlobalStateController( - mCarNavigationBarController, mSystemUIOverlayWindowController); - mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( - R.layout.sysui_overlay_window, /* root= */ null); - when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); - mCarKeyguardViewController = new TestableCarKeyguardViewController( mContext, Handler.getMain(), @@ -98,6 +90,8 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase { mock(FalsingManager.class), () -> mock(KeyguardBypassController.class) ); + mCarKeyguardViewController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.sysui_overlay_window, /* root= */ null)); } @Test @@ -113,8 +107,7 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase { when(mBouncer.isSecure()).thenReturn(true); mCarKeyguardViewController.show(/* options= */ null); - assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( - View.VISIBLE); + verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController), any()); } @Test @@ -130,8 +123,17 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase { when(mBouncer.isSecure()).thenReturn(false); mCarKeyguardViewController.show(/* options= */ null); - assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( - View.GONE); + // Here we check for both showView and hideView since the current implementation of show + // with bouncer being not secure has the following method execution orders: + // 1) show -> start -> showView + // 2) show -> reset -> dismissAndCollapse -> hide -> stop -> hideView + // Hence, we want to make sure that showView is called before hideView and not in any + // other combination. + InOrder inOrder = inOrder(mOverlayViewGlobalStateController); + inOrder.verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController), + any()); + inOrder.verify(mOverlayViewGlobalStateController).hideView(eq(mCarKeyguardViewController), + any()); } @Test @@ -156,8 +158,11 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase { mCarKeyguardViewController.show(/* options= */ null); mCarKeyguardViewController.hide(/* startTime= */ 0, /* fadeoutDelay= */ 0); - assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( - View.GONE); + InOrder inOrder = inOrder(mOverlayViewGlobalStateController); + inOrder.verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController), + any()); + inOrder.verify(mOverlayViewGlobalStateController).hideView(eq(mCarKeyguardViewController), + any()); } @Test diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java index 6ac72a681bfe..ccaeb458fe54 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java @@ -28,9 +28,9 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.car.window.OverlayViewGlobalStateController; import org.junit.Before; import org.junit.Test; @@ -42,12 +42,11 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper @SmallTest public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { - private CarHeadsUpNotificationSystemContainer mDefaultController; - private CarHeadsUpNotificationSystemContainer mOverrideEnabledController; + private CarHeadsUpNotificationSystemContainer mCarHeadsUpNotificationSystemContainer; @Mock private CarDeviceProvisionedController mCarDeviceProvisionedController; @Mock - private NotificationPanelViewController mNotificationPanelViewController; + private OverlayViewGlobalStateController mOverlayViewGlobalStateController; @Mock private WindowManager mWindowManager; @@ -58,76 +57,63 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.initMocks(/* testClass= */this); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); - when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); - when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false); + when(mOverlayViewGlobalStateController.shouldShowHUN()).thenReturn(true); + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true); TestableResources testableResources = mContext.getOrCreateTestableResources(); - testableResources.addOverride( - R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, false); - - mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext, - testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager, - () -> mNotificationPanelViewController); - - testableResources.addOverride( - R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true); - - mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext, + mCarHeadsUpNotificationSystemContainer = new CarHeadsUpNotificationSystemContainer(mContext, testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager, - () -> mNotificationPanelViewController); + mOverlayViewGlobalStateController); } @Test public void testDisplayNotification_firstNotification_isVisible() { - mDefaultController.displayNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isTrue(); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue(); } @Test public void testRemoveNotification_lastNotification_isInvisible() { - mDefaultController.displayNotification(mNotificationView); - mDefaultController.removeNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isFalse(); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + mCarHeadsUpNotificationSystemContainer.removeNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse(); } @Test public void testRemoveNotification_nonLastNotification_isVisible() { - mDefaultController.displayNotification(mNotificationView); - mDefaultController.displayNotification(mNotificationView2); - mDefaultController.removeNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isTrue(); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView2); + mCarHeadsUpNotificationSystemContainer.removeNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue(); } @Test - public void testDisplayNotification_userSetupInProgress_isInvisible() { - when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(true); - mDefaultController.displayNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isFalse(); + public void testDisplayNotification_userFullySetupTrue_isInvisible() { + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue(); } @Test - public void testDisplayNotification_userSetupIncomplete_isInvisible() { - when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false); - mDefaultController.displayNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isFalse(); + public void testDisplayNotification_userFullySetupFalse_isInvisible() { + when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse(); } @Test - public void testDisplayNotification_notificationPanelExpanded_isInvisible() { - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); - mDefaultController.displayNotification(mNotificationView); - assertThat(mDefaultController.isVisible()).isFalse(); + public void testDisplayNotification_overlayWindowStateShouldShowHUNFalse_isInvisible() { + when(mOverlayViewGlobalStateController.shouldShowHUN()).thenReturn(false); + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse(); } @Test - public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() { - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); - mOverrideEnabledController.displayNotification(mNotificationView); - assertThat(mOverrideEnabledController.isVisible()).isTrue(); + public void testDisplayNotification_overlayWindowStateShouldShowHUNTrue_isVisible() { + mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView); + assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue(); } } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java index 8d705a8cca1f..45a05ac69bd7 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java @@ -339,7 +339,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { mOverlayPanelViewController.setPanelVisible(true); - verify(mOverlayViewGlobalStateController).setWindowVisible(true); + verify(mOverlayViewGlobalStateController).showView(mOverlayPanelViewController); } @Test @@ -349,7 +349,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { mOverlayPanelViewController.setPanelVisible(true); - verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true); + verify(mOverlayViewGlobalStateController, never()).showView(mOverlayPanelViewController); } @Test @@ -377,7 +377,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { mOverlayPanelViewController.setPanelVisible(false); - verify(mOverlayViewGlobalStateController).setWindowVisible(false); + verify(mOverlayViewGlobalStateController).hideView(mOverlayPanelViewController); } @Test @@ -387,7 +387,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { mOverlayPanelViewController.setPanelVisible(false); - verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false); + verify(mOverlayViewGlobalStateController, never()).hideView(mOverlayPanelViewController); } @Test @@ -428,10 +428,6 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { private static class TestOverlayPanelViewController extends OverlayPanelViewController { - private boolean mShouldAnimateCollapsePanel; - private boolean mShouldAnimateExpandPanel; - private boolean mShouldAllowClosingScroll; - boolean mOnAnimateCollapsePanelCalled; boolean mAnimateCollapsePanelCalled; boolean mOnAnimateExpandPanelCalled; @@ -440,6 +436,9 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase { boolean mOnExpandAnimationEndCalled; boolean mOnOpenScrollStartEnd; List<Integer> mOnScrollHeights; + private boolean mShouldAnimateCollapsePanel; + private boolean mShouldAnimateExpandPanel; + private boolean mShouldAllowClosingScroll; TestOverlayPanelViewController( Context context, diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java index 25dd4f502fb7..9e6e616e3ccf 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java @@ -24,25 +24,33 @@ import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; +import android.view.ViewStub; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.navigationbar.CarNavigationBarController; +import com.android.systemui.tests.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Arrays; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { - private static final String MOCK_OVERLAY_VIEW_CONTROLLER_NAME = "OverlayViewController"; + private static final int OVERLAY_VIEW_CONTROLLER_1_Z_ORDER = 0; + private static final int OVERLAY_VIEW_CONTROLLER_2_Z_ORDER = 1; + private static final int OVERLAY_PANEL_VIEW_CONTROLLER_Z_ORDER = 2; private OverlayViewGlobalStateController mOverlayViewGlobalStateController; private ViewGroup mBaseLayout; @@ -54,7 +62,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { @Mock private OverlayViewMediator mOverlayViewMediator; @Mock - private OverlayViewController mOverlayViewController; + private OverlayViewController mOverlayViewController1; + @Mock + private OverlayViewController mOverlayViewController2; + @Mock + private OverlayPanelViewController mOverlayPanelViewController; @Mock private Runnable mRunnable; @@ -62,14 +74,15 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); + mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.overlay_view_global_state_controller_test, /* root= */ null); + + when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); + mOverlayViewGlobalStateController = new OverlayViewGlobalStateController( mCarNavigationBarController, mSystemUIOverlayWindowController); verify(mSystemUIOverlayWindowController).attach(); - - mBaseLayout = new FrameLayout(mContext); - - when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); } @Test @@ -87,182 +100,445 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { } @Test - public void showView_nothingAlreadyShown_navigationBarsHidden() { - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + public void showView_nothingAlreadyShown_shouldShowNavBarFalse_navigationBarsHidden() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); verify(mCarNavigationBarController).hideBars(); } @Test - public void showView_nothingAlreadyShown_windowIsExpanded() { - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + public void showView_nothingAlreadyShown_shouldShowNavBarTrue_navigationBarsShown() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mCarNavigationBarController).showBars(); + } + + @Test + public void showView_nothingAlreadyShown_windowIsSetVisible() { + setupOverlayViewController1(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); verify(mSystemUIOverlayWindowController).setWindowVisible(true); } @Test - public void showView_somethingAlreadyShown_navigationBarsHidden() { - mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME); + public void showView_nothingAlreadyShown_newHighestZOrder() { + setupOverlayViewController1(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController1); + } + + @Test + public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() { + setupOverlayViewController1(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey( + OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue(); + } + + @Test + public void showView_newHighestZOrder() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController, never()).hideBars(); + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController2); } @Test - public void showView_somethingAlreadyShown_windowIsExpanded() { - mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME); + public void showView_newHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + verify(mCarNavigationBarController).hideBars(); + } + + @Test + public void showView_newHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mCarNavigationBarController).showBars(); + } + + @Test + public void showView_newHighestZOrder_correctViewsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.keySet().toArray()) + .isEqualTo(Arrays.asList(OVERLAY_VIEW_CONTROLLER_1_Z_ORDER, + OVERLAY_VIEW_CONTROLLER_2_Z_ORDER).toArray()); + } + + @Test + public void showView_oldHighestZOrder() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController2); + } + + @Test + public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mCarNavigationBarController).hideBars(); + } + + @Test + public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mCarNavigationBarController).showBars(); + } + + @Test + public void showView_oldHighestZOrder_correctViewsShown() { + setupOverlayViewController1(); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.keySet().toArray()) + .isEqualTo(Arrays.asList(OVERLAY_VIEW_CONTROLLER_1_Z_ORDER, + OVERLAY_VIEW_CONTROLLER_2_Z_ORDER).toArray()); + } + + @Test + public void showView_somethingAlreadyShown_windowVisibleNotCalled() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); verify(mSystemUIOverlayWindowController, never()).setWindowVisible(true); } @Test public void showView_viewControllerNotInflated_inflateViewController() { - when(mOverlayViewController.isInflated()).thenReturn(false); + setupOverlayViewController2(); + when(mOverlayViewController2.isInflated()).thenReturn(false); - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mOverlayViewController).inflate(mBaseLayout); + verify(mOverlayViewController2).inflate(mBaseLayout); } @Test public void showView_viewControllerInflated_inflateViewControllerNotCalled() { - when(mOverlayViewController.isInflated()).thenReturn(true); + setupOverlayViewController2(); - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mOverlayViewController, never()).inflate(mBaseLayout); + verify(mOverlayViewController2, never()).inflate(mBaseLayout); } @Test - public void showView_showRunnableCalled() { - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + public void showView_panelViewController_inflateViewControllerNotCalled() { + setupOverlayPanelViewController(); - verify(mRunnable).run(); + mOverlayViewGlobalStateController.showView(mOverlayPanelViewController, mRunnable); + + verify(mOverlayPanelViewController, never()).inflate(mBaseLayout); + verify(mOverlayPanelViewController, never()).isInflated(); } @Test - public void showView_overlayViewControllerAddedToShownSet() { - mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable); + public void showView_showRunnableCalled() { + setupOverlayViewController1(); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - assertThat(mOverlayViewGlobalStateController.mShownSet.contains( - mOverlayViewController.getClass().getName())).isTrue(); + verify(mRunnable).run(); } @Test public void hideView_viewControllerNotInflated_hideRunnableNotCalled() { - when(mOverlayViewController.isInflated()).thenReturn(false); + when(mOverlayViewController2.isInflated()).thenReturn(false); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); verify(mRunnable, never()).run(); } @Test public void hideView_nothingShown_hideRunnableNotCalled() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.clear(); + when(mOverlayViewController2.isInflated()).thenReturn(true); + mOverlayViewGlobalStateController.mZOrderMap.clear(); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); verify(mRunnable, never()).run(); } @Test public void hideView_viewControllerNotShown_hideRunnableNotCalled() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + when(mOverlayViewController2.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); verify(mRunnable, never()).run(); } @Test public void hideView_viewControllerShown_hideRunnableCalled() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); verify(mRunnable).run(); } @Test + public void hideView_viewControllerOnlyShown_noHighestZOrder() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isNull(); + } + + @Test public void hideView_viewControllerOnlyShown_nothingShown() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.isEmpty()).isTrue(); + } + + @Test + public void hideView_viewControllerOnlyShown_viewControllerNotShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey( + OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isFalse(); + } + + @Test + public void hideView_newHighestZOrder_twoViewsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController1); + } + + @Test + public void hideView_newHighestZOrder_threeViewsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + setupOverlayPanelViewController(); + setOverlayViewControllerAsShowing(mOverlayPanelViewController); + + mOverlayViewGlobalStateController.hideView(mOverlayPanelViewController, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController2); + } + + @Test + public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mCarNavigationBarController).hideBars(); + } + + @Test + public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + verify(mCarNavigationBarController).showBars(); + } + + @Test + public void hideView_oldHighestZOrder() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); - assertThat(mOverlayViewGlobalStateController.mShownSet.isEmpty()).isTrue(); + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo( + mOverlayViewController2); } @Test - public void hideView_viewControllerNotOnlyShown_navigationBarNotShown() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); - mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME); + public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController, never()).showBars(); + verify(mCarNavigationBarController).hideBars(); + } + + @Test + public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mCarNavigationBarController).showBars(); } @Test public void hideView_viewControllerNotOnlyShown_windowNotCollapsed() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); - mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); verify(mSystemUIOverlayWindowController, never()).setWindowVisible(false); } @Test public void hideView_viewControllerOnlyShown_navigationBarShown() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); verify(mCarNavigationBarController).showBars(); } @Test public void hideView_viewControllerOnlyShown_windowCollapsed() { - when(mOverlayViewController.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.mShownSet.add( - mOverlayViewController.getClass().getName()); + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); - mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable); + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); verify(mSystemUIOverlayWindowController).setWindowVisible(false); } @Test public void inflateView_notInflated_inflates() { - when(mOverlayViewController.isInflated()).thenReturn(false); + when(mOverlayViewController2.isInflated()).thenReturn(false); - mOverlayViewGlobalStateController.inflateView(mOverlayViewController); + mOverlayViewGlobalStateController.inflateView(mOverlayViewController2); - verify(mOverlayViewController).inflate(mBaseLayout); + verify(mOverlayViewController2).inflate(mBaseLayout); } @Test public void inflateView_alreadyInflated_doesNotInflate() { - when(mOverlayViewController.isInflated()).thenReturn(true); + when(mOverlayViewController2.isInflated()).thenReturn(true); - mOverlayViewGlobalStateController.inflateView(mOverlayViewController); + mOverlayViewGlobalStateController.inflateView(mOverlayViewController2); + + verify(mOverlayViewController2, never()).inflate(mBaseLayout); + } + + private void setupOverlayViewController1() { + setupOverlayViewController(mOverlayViewController1, R.id.overlay_view_controller_stub_1, + R.id.overlay_view_controller_1); + } - verify(mOverlayViewController, never()).inflate(mBaseLayout); + private void setupOverlayViewController2() { + setupOverlayViewController(mOverlayViewController2, R.id.overlay_view_controller_stub_2, + R.id.overlay_view_controller_2); + } + + private void setupOverlayPanelViewController() { + setupOverlayViewController(mOverlayPanelViewController, R.id.overlay_view_controller_stub_3, + R.id.overlay_view_controller_3); + } + + private void setupOverlayViewController(OverlayViewController overlayViewController, + int stubId, int inflatedId) { + ViewStub viewStub = mBaseLayout.findViewById(stubId); + View layout; + if (viewStub == null) { + layout = mBaseLayout.findViewById(inflatedId); + } else { + layout = viewStub.inflate(); + } + when(overlayViewController.getLayout()).thenReturn(layout); + when(overlayViewController.isInflated()).thenReturn(true); + } + + private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) { + mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null); + Mockito.reset(mCarNavigationBarController, mSystemUIOverlayWindowController); + when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index a95677d0202f..5675c9986ac9 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -38,8 +38,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PackageParser; -import android.content.pm.PackageUserState; import android.net.Uri; import android.os.Bundle; import android.os.Process; @@ -526,18 +524,16 @@ public class PackageInstallerActivity extends AlertActivity { case ContentResolver.SCHEME_FILE: { File sourceFile = new File(packageUri.getPath()); - PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile); + mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile, + PackageManager.GET_PERMISSIONS); // Check for parse errors - if (parsed == null) { + if (mPkgInfo == null) { Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); showDialogInner(DLG_PACKAGE_ERROR); setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); return false; } - mPkgInfo = PackageParser.generatePackageInfo(parsed, null, - PackageManager.GET_PERMISSIONS, 0, 0, null, - new PackageUserState()); mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); } break; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 0e89f56d2376..d3a9f8fe1196 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -22,9 +22,8 @@ import android.annotation.Nullable; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageParserException; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -53,12 +52,12 @@ public class PackageUtil { /** * Utility method to get package information for a given {@link File} */ - public static PackageParser.Package getPackageInfo(Context context, File sourceFile) { - final PackageParser parser = new PackageParser(); - parser.setCallback(new PackageParser.CallbackImpl(context.getPackageManager())); + @Nullable + public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) { try { - return parser.parsePackage(sourceFile, 0); - } catch (PackageParserException e) { + return context.getPackageManager().getPackageArchiveInfo(sourceFile.getAbsolutePath(), + flags); + } catch (Exception ignored) { return null; } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java index e5f7613ab52b..06b1c1635644 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java @@ -22,11 +22,11 @@ import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDeleteObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.database.Cursor; import android.net.Uri; import android.os.Build; @@ -49,6 +49,7 @@ import com.android.packageinstaller.R; import java.io.File; import java.io.FileNotFoundException; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -244,47 +245,50 @@ public class WearPackageInstallerService extends Service { Log.e(TAG, "Could not create a temp file from FD for " + packageName); return; } - PackageParser.Package pkg = PackageUtil.getPackageInfo(this, tempFile); - if (pkg == null) { + PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile, + PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS); + if (pkgInfo == null) { Log.e(TAG, "Could not parse apk information for " + packageName); return; } - if (!pkg.packageName.equals(packageName)) { + if (!pkgInfo.packageName.equals(packageName)) { Log.e(TAG, "Wearable Package Name has to match what is provided for " + packageName); return; } - pkg.applicationInfo.sourceDir = tempFile.getPath(); - pkg.applicationInfo.publicSourceDir = tempFile.getPath(); + ApplicationInfo appInfo = pkgInfo.applicationInfo; + appInfo.sourceDir = tempFile.getPath(); + appInfo.publicSourceDir = tempFile.getPath(); getLabelAndUpdateNotification(packageName, - getString(R.string.installing_app, pkg.applicationInfo.loadLabel(pm))); + getString(R.string.installing_app, appInfo.loadLabel(pm))); - List<String> wearablePerms = pkg.requestedPermissions; + List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions); // Log if the installed pkg has a higher version number. if (existingPkgInfo != null) { - if (existingPkgInfo.getLongVersionCode() == pkg.getLongVersionCode()) { + long longVersionCode = pkgInfo.getLongVersionCode(); + if (existingPkgInfo.getLongVersionCode() == longVersionCode) { if (skipIfSameVersion) { - Log.w(TAG, "Version number (" + pkg.getLongVersionCode() + + Log.w(TAG, "Version number (" + longVersionCode + ") of new app is equal to existing app for " + packageName + "; not installing due to versionCheck"); return; } else { - Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() + + Log.w(TAG, "Version number of new app (" + longVersionCode + ") is equal to existing app for " + packageName); } - } else if (existingPkgInfo.getLongVersionCode() > pkg.getLongVersionCode()) { + } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) { if (skipIfLowerVersion) { // Starting in Feldspar, we are not going to allow downgrades of any app. - Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() + + Log.w(TAG, "Version number of new app (" + longVersionCode + ") is lower than existing app ( " + existingPkgInfo.getLongVersionCode() + ") for " + packageName + "; not installing due to versionCheck"); return; } else { - Log.w(TAG, "Version number of new app (" + pkg.getLongVersionCode() + + Log.w(TAG, "Version number of new app (" + longVersionCode + ") is lower than existing app ( " + existingPkgInfo.getLongVersionCode() + ") for " + packageName); } @@ -309,14 +313,12 @@ public class WearPackageInstallerService extends Service { // Check that the wearable has all the features. boolean hasAllFeatures = true; - if (pkg.reqFeatures != null) { - for (FeatureInfo feature : pkg.reqFeatures) { - if (feature.name != null && !pm.hasSystemFeature(feature.name) && - (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) { - Log.e(TAG, "Wearable does not have required feature: " + feature + - " for " + packageName); - hasAllFeatures = false; - } + for (FeatureInfo feature : pkgInfo.reqFeatures) { + if (feature.name != null && !pm.hasSystemFeature(feature.name) && + (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) { + Log.e(TAG, "Wearable does not have required feature: " + feature + + " for " + packageName); + hasAllFeatures = false; } } @@ -328,8 +330,8 @@ public class WearPackageInstallerService extends Service { // wearable package. // If the app is targeting API level 23, we will also start a service in ClockworkHome // which will ultimately prompt the user to accept/reject permissions. - if (checkPerms && !checkPermissions(pkg, companionSdkVersion, companionDeviceVersion, - permUri, wearablePerms, tempFile)) { + if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion, + companionDeviceVersion, permUri, wearablePerms, tempFile)) { Log.w(TAG, "Wearable does not have enough permissions."); return; } @@ -382,7 +384,7 @@ public class WearPackageInstallerService extends Service { } } - private boolean checkPermissions(PackageParser.Package pkg, int companionSdkVersion, + private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion, int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, File apkFile) { // Assumption: We are running on Android O. @@ -390,12 +392,12 @@ public class WearPackageInstallerService extends Service { // app. If the Wear App is then not targeting M, there may be permissions that are not // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear // app. - if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { + if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { // Install the app if Wear App is ready for the new perms model. return true; } - if (!doesWearHaveUngrantedPerms(pkg.packageName, permUri, wearablePermissions)) { + if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) { // All permissions requested by the watch are already granted on the phone, no need // to do anything. return true; diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index 7b58937049d1..d59d698efba2 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -633,4 +633,18 @@ <item>@color/bt_color_bg_7</item> </integer-array> + <!-- Cached apps freezer modes --> + <array name="cached_apps_freezer_entries"> + <item>@string/cached_apps_freezer_device_default</item> + <item>@string/cached_apps_freezer_enabled</item> + <item>@string/cached_apps_freezer_disabled</item> + </array> + + <!-- Values for cached apps freezer modes --> + <array name="cached_apps_freezer_values"> + <item>device_default</item> + <item>enabled</item> + <item>disabled</item> + </array> + </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 7ca0e809143a..934f61091bcc 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1365,4 +1365,12 @@ <!-- Name for the guest user [CHAR LIMIT=35] --> <string name="guest_nickname">Guest</string> + <!-- List entry in developer settings to choose default device/system behavior for the app freezer [CHAR LIMIT=30]--> + <string name="cached_apps_freezer_device_default">Device default</string> + <!-- List entry in developer settings to disable the app freezer in developer settings [CHAR LIMIT=30]--> + <string name="cached_apps_freezer_disabled">Disabled</string> + <!-- List entry in developer settings to enable the app freezer in developer settings [CHAR LIMIT=30]--> + <string name="cached_apps_freezer_enabled">Enabled</string> + <!-- Developer setting dialog prompting the user to reboot after changing the app freezer setting [CHAR LIMIT=NONE]--> + <string name="cached_apps_freezer_reboot_dialog_text">Your device must be rebooted for this change to apply. Reboot now or cancel.</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index c4ff71940d20..ae3194df28fe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -140,4 +140,15 @@ public class AppUtils { .isSystemModule(packageName); } + /** + * Returns a boolean indicating whether a given package is a mainline module. + */ + public static boolean isMainlineModule(Context context, String packageName) { + final PackageManager pm = context.getPackageManager(); + try { + return pm.getModuleInfo(packageName, 0 /* flags */) != null; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 436f2fa87394..1d06df08bd3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -108,10 +108,8 @@ public class InfoMediaManager extends MediaManager { final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions(); if (infos.size() > 0) { final RoutingSessionInfo info = infos.get(0); - final MediaRouter2Manager.RoutingController controller = - mRouterManager.getControllerForSession(info); + mRouterManager.transfer(info, device.mRouteInfo); - controller.transferToRoute(device.mRouteInfo); isConnected = true; } return isConnected; @@ -131,7 +129,7 @@ public class InfoMediaManager extends MediaManager { final RoutingSessionInfo info = getRoutingSessionInfo(); if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) { - mRouterManager.getControllerForSession(info).selectRoute(device.mRouteInfo); + mRouterManager.selectRoute(info, device.mRouteInfo); return true; } @@ -162,7 +160,7 @@ public class InfoMediaManager extends MediaManager { final RoutingSessionInfo info = getRoutingSessionInfo(); if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) { - mRouterManager.getControllerForSession(info).deselectRoute(device.mRouteInfo); + mRouterManager.deselectRoute(info, device.mRouteInfo); return true; } @@ -207,8 +205,7 @@ public class InfoMediaManager extends MediaManager { final RoutingSessionInfo info = getRoutingSessionInfo(); if (info != null) { - for (MediaRoute2Info route : mRouterManager.getControllerForSession(info) - .getSelectableRoutes()) { + for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) { deviceList.add(new InfoMediaDevice(mContext, mRouterManager, route, mPackageName)); } @@ -235,8 +232,7 @@ public class InfoMediaManager extends MediaManager { final RoutingSessionInfo info = getRoutingSessionInfo(); if (info != null) { - for (MediaRoute2Info route : mRouterManager.getControllerForSession(info) - .getSelectedRoutes()) { + for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) { deviceList.add(new InfoMediaDevice(mContext, mRouterManager, route, mPackageName)); } @@ -434,7 +430,7 @@ public class InfoMediaManager extends MediaManager { } @Override - public void onControlCategoriesChanged(String packageName, List<String> controlCategories) { + public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) { if (TextUtils.equals(mPackageName, packageName)) { refreshDevices(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 31ea5b40d756..887a49b95279 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -27,10 +27,13 @@ import android.util.Log; import androidx.annotation.IntDef; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -430,7 +433,8 @@ public class LocalMediaManager implements BluetoothCallback { cachedDeviceManager.findDevice(device); if (cachedDevice != null) { if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED - && !cachedDevice.isConnected()) { + && !cachedDevice.isConnected() + && isA2dpOrHearingAidDevice(cachedDevice)) { deviceCount++; cachedBluetoothDeviceList.add(cachedDevice); if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) { @@ -454,6 +458,15 @@ public class LocalMediaManager implements BluetoothCallback { return new ArrayList<>(mDisconnectedMediaDevices); } + private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) { + for (LocalBluetoothProfile profile : device.getConnectableProfiles()) { + if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) { + return true; + } + } + return false; + } + @Override public void onDeviceRemoved(MediaDevice device) { if (mMediaDevices.contains(device)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index f1c0f6b44150..139a12c44e0f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -31,18 +31,14 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.text.TextUtils; -import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; -import com.android.settingslib.R; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -180,7 +176,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ public void requestSetVolume(int volume) { - mRouterManager.requestSetVolume(mRouteInfo, volume); + mRouterManager.setRouteVolume(mRouteInfo, volume); } /** @@ -215,30 +211,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * * @return application label. */ - public String getClientAppLabel() { - final String packageName = mRouteInfo.getClientPackageName(); - if (TextUtils.isEmpty(packageName)) { - Log.d(TAG, "Client package name is empty"); - return mContext.getResources().getString(R.string.unknown); - } - try { - final PackageManager packageManager = mContext.getPackageManager(); - final String appLabel = packageManager.getApplicationLabel( - packageManager.getApplicationInfo(packageName, 0)).toString(); - if (!TextUtils.isEmpty(appLabel)) { - return appLabel; - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "unable to find " + packageName); - } - return mContext.getResources().getString(R.string.unknown); - } - - /** - * Get application label from MediaDevice. - * - * @return application label. - */ public int getDeviceType() { return mType; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 42f2542e5c30..8ea5ff1bf662 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -41,7 +41,10 @@ public class PhoneMediaDevice extends MediaDevice { private static final String TAG = "PhoneMediaDevice"; - public static final String ID = "phone_media_device_id_1"; + public static final String PHONE_ID = "phone_media_device_id"; + // For 3.5 mm wired headset + public static final String WIRED_HEADSET_ID = "wired_headset_media_device_id"; + public static final String USB_HEADSET_ID = "usb_headset_media_device_id"; private String mSummary = ""; @@ -109,7 +112,25 @@ public class PhoneMediaDevice extends MediaDevice { @Override public String getId() { - return ID; + String id; + switch (mRouteInfo.getType()) { + case TYPE_WIRED_HEADSET: + case TYPE_WIRED_HEADPHONES: + id = WIRED_HEADSET_ID; + break; + case TYPE_USB_DEVICE: + case TYPE_USB_HEADSET: + case TYPE_USB_ACCESSORY: + case TYPE_DOCK: + case TYPE_HDMI: + id = USB_HEADSET_ID; + break; + case TYPE_BUILTIN_SPEAKER: + default: + id = PHONE_ID; + break; + } + return id; } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 2ed9c7b94d89..99c568a4707b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -136,7 +136,7 @@ public class InfoMediaManagerTest { } @Test - public void onControlCategoriesChanged_samePackageName_shouldAddMediaDevice() { + public void onPreferredFeaturesChanged_samePackageName_shouldAddMediaDevice() { final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); routingSessionInfos.add(sessionInfo); @@ -156,7 +156,7 @@ public class InfoMediaManagerTest { final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); assertThat(mediaDevice).isNull(); - mInfoMediaManager.mMediaRouterCallback.onControlCategoriesChanged(TEST_PACKAGE_NAME, null); + mInfoMediaManager.mMediaRouterCallback.onPreferredFeaturesChanged(TEST_PACKAGE_NAME, null); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); assertThat(infoDevice.getId()).isEqualTo(TEST_ID); @@ -165,8 +165,8 @@ public class InfoMediaManagerTest { } @Test - public void onControlCategoriesChanged_differentPackageName_doNothing() { - mInfoMediaManager.mMediaRouterCallback.onControlCategoriesChanged("com.fake.play", null); + public void onPreferredFeaturesChanged_differentPackageName_doNothing() { + mInfoMediaManager.mMediaRouterCallback.onPreferredFeaturesChanged("com.fake.play", null); assertThat(mInfoMediaManager.mMediaDevices).hasSize(0); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 77316e91bae2..365a16c50b99 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -41,6 +41,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; @@ -560,6 +561,10 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDevices.add(device3); mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice); + final List<LocalBluetoothProfile> profiles = new ArrayList<>(); + final A2dpProfile a2dpProfile = mock(A2dpProfile.class); + profiles.add(a2dpProfile); + final List<BluetoothDevice> bluetoothDevices = new ArrayList<>(); final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); @@ -571,6 +576,7 @@ public class LocalMediaManagerTest { when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(cachedDevice.isConnected()).thenReturn(false); + when(cachedDevice.getConnectableProfiles()).thenReturn(profiles); when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); @@ -634,6 +640,10 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDevices.add(device3); mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice); + final List<LocalBluetoothProfile> profiles = new ArrayList<>(); + final A2dpProfile a2dpProfile = mock(A2dpProfile.class); + profiles.add(a2dpProfile); + final List<BluetoothDevice> bluetoothDevices = new ArrayList<>(); final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class); @@ -662,6 +672,7 @@ public class LocalMediaManagerTest { when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(cachedDevice.isConnected()).thenReturn(false); when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); + when(cachedDevice.getConnectableProfiles()).thenReturn(profiles); when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass); when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 6664870a6257..47d4beb752c5 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -29,13 +29,9 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; -import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidProfile; @@ -49,8 +45,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; -import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; import java.util.Collections; @@ -70,8 +64,6 @@ public class MediaDeviceTest { private static final String ROUTER_ID_2 = "RouterId_2"; private static final String ROUTER_ID_3 = "RouterId_3"; private static final String TEST_PACKAGE_NAME = "com.test.playmusic"; - private static final String TEST_PACKAGE_NAME2 = "com.test.playmusic2"; - private static final String TEST_APPLICATION_LABEL = "playmusic"; private final BluetoothClass mHeadreeClass = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass mCarkitClass = @@ -125,10 +117,6 @@ public class MediaDeviceTest { private InfoMediaDevice mInfoMediaDevice3; private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; - private ShadowPackageManager mShadowPackageManager; - private ApplicationInfo mAppInfo; - private PackageInfo mPackageInfo; - private PackageStats mPackageStats; @Before public void setUp() { @@ -459,41 +447,6 @@ public class MediaDeviceTest { assertThat(mInfoMediaDevice1.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); } - private void initPackage() { - mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); - mAppInfo = new ApplicationInfo(); - mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; - mAppInfo.packageName = TEST_PACKAGE_NAME; - mAppInfo.name = TEST_APPLICATION_LABEL; - mPackageInfo = new PackageInfo(); - mPackageInfo.packageName = TEST_PACKAGE_NAME; - mPackageInfo.applicationInfo = mAppInfo; - mPackageStats = new PackageStats(TEST_PACKAGE_NAME); - } - - @Test - public void getClientAppLabel_matchedPackageName_returnLabel() { - initPackage(); - when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - - assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(TEST_APPLICATION_LABEL); - } - - @Test - public void getClientAppLabel_noMatchedPackageName_returnDefault() { - initPackage(); - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); - - assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - } - @Test public void setState_verifyGetState() { mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 6f265dd603e5..47f6fe3bce02 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -21,6 +21,10 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID; +import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID; +import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; @@ -108,4 +112,22 @@ public class PhoneMediaDeviceTest { assertThat(mPhoneMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name)); } + + @Test + public void getId_returnCorrectId() { + when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); + + assertThat(mPhoneMediaDevice.getId()) + .isEqualTo(WIRED_HEADSET_ID); + + when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE); + + assertThat(mPhoneMediaDevice.getId()) + .isEqualTo(USB_HEADSET_ID); + + when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); + + assertThat(mPhoneMediaDevice.getId()) + .isEqualTo(PHONE_ID); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index a5dce6da348f..3d7559b2c1a6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -376,6 +376,9 @@ class SettingsProtoDumpUtil { Settings.Global.BUGREPORT_IN_POWER_MENU, GlobalSettingsProto.BUGREPORT_IN_POWER_MENU); dumpSetting(s, p, + Settings.Global.CACHED_APPS_FREEZER_ENABLED, + GlobalSettingsProto.CACHED_APPS_FREEZER_ENABLED); + dumpSetting(s, p, Settings.Global.CALL_AUTO_RETRY, GlobalSettingsProto.CALL_AUTO_RETRY); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index be0a8640760f..4a9eba2202e3 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -587,7 +587,8 @@ public class SettingsBackupTest { Settings.Global.POWER_BUTTON_VERY_LONG_PRESS, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, // Temporary for R beta Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT); + Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT, + Settings.Global.CACHED_APPS_FREEZER_ENABLED); private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml index 2821e4c28bab..e79c9a40918c 100644 --- a/packages/SystemUI/res/drawable/qs_media_background.xml +++ b/packages/SystemUI/res/drawable/qs_media_background.xml @@ -14,13 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="?android:attr/colorBackgroundFloating" /> - <corners - android:bottomLeftRadius="@dimen/qs_media_corner_radius" - android:topLeftRadius="@dimen/qs_media_corner_radius" - android:bottomRightRadius="@dimen/qs_media_corner_radius" - android:topRightRadius="@dimen/qs_media_corner_radius" - /> -</shape>
\ No newline at end of file +<com.android.systemui.media.IlluminationDrawable + xmlns:systemui="http://schemas.android.com/apk/res-auto" + systemui:rippleMinSize="30dp" + systemui:rippleMaxSize="135dp" + systemui:highlight="15" + systemui:cornerRadius="@dimen/qs_media_corner_radius" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml index 163015b7b0f0..21013c6c7b16 100644 --- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml +++ b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml @@ -17,6 +17,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:angle="90" - android:startColor="#1f000000" + android:startColor="@color/global_screenshot_background_protection_start" android:endColor="#00000000"/> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml index 88a05ec5824a..1ed1f07fb277 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_view.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml @@ -36,6 +36,8 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:maxLines="1" + android:lines="2" + android:ellipsize="end" android:layout_gravity="center" android:paddingTop="@dimen/bubble_overflow_text_padding" android:gravity="center"/> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index de19303b4948..1dbb38d5dc7a 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -22,7 +22,7 @@ android:layout_height="match_parent"> <ImageView android:id="@+id/global_screenshot_actions_background" - android:layout_height="@dimen/global_screenshot_bg_protection_height" + android:layout_height="@dimen/screenshot_bg_protection_height" android:layout_width="match_parent" android:alpha="0.0" android:src="@drawable/screenshot_actions_background_protection" diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml index de9ef218083b..20ec10ca1e1b 100644 --- a/packages/SystemUI/res/layout/keyguard_media_header.xml +++ b/packages/SystemUI/res/layout/keyguard_media_header.xml @@ -124,7 +124,7 @@ android:layout_gravity="center" > <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" @@ -132,7 +132,7 @@ android:id="@+id/action0" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" @@ -140,7 +140,7 @@ android:id="@+id/action1" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml index 403b5dc3a427..2e86732f3cad 100644 --- a/packages/SystemUI/res/layout/qqs_media_panel.xml +++ b/packages/SystemUI/res/layout/qqs_media_panel.xml @@ -63,7 +63,7 @@ android:gravity="center" > <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" @@ -71,7 +71,7 @@ android:id="@+id/action0" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" @@ -79,7 +79,7 @@ android:id="@+id/action1" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:gravity="center" diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml index a194569dcca4..d633ff40df9e 100644 --- a/packages/SystemUI/res/layout/qs_media_panel.xml +++ b/packages/SystemUI/res/layout/qs_media_panel.xml @@ -197,7 +197,7 @@ android:gravity="center" > <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="8dp" @@ -207,7 +207,7 @@ android:id="@+id/action0" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="8dp" @@ -217,7 +217,7 @@ android:id="@+id/action1" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="52dp" android:layout_height="52dp" android:layout_marginStart="8dp" @@ -227,7 +227,7 @@ android:id="@+id/action2" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="8dp" @@ -237,7 +237,7 @@ android:id="@+id/action3" /> <ImageButton - style="@android:style/Widget.Material.Button.Borderless.Small" + style="@style/MediaPlayer.Button" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="8dp" diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 2d5101104237..196357c4794e 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -79,6 +79,7 @@ <color name="global_screenshot_button_icon">@color/GM2_blue_300</color> <color name="global_screenshot_dismiss_background">@color/GM2_grey_800</color> <color name="global_screenshot_dismiss_foreground">#FFFFFF</color> + <color name="global_screenshot_background_protection_start">#80000000</color> <!-- 50% black --> <!-- Biometric dialog colors --> diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml index 481483991de9..23e323112845 100644 --- a/packages/SystemUI/res/values-night/dimens.xml +++ b/packages/SystemUI/res/values-night/dimens.xml @@ -18,4 +18,8 @@ <resources> <!-- The height of the divider between the individual notifications. --> <dimen name="notification_divider_height">1dp</dimen> + + <!-- Height of the background gradient behind the screenshot UI (taller in dark mode) --> + <dimen name="screenshot_bg_protection_height">375dp</dimen> + </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index d3256ef34bd7..c4195940d11a 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -154,5 +154,12 @@ <declare-styleable name="CaptionsToggleImageButton"> <attr name="optedOut" format="boolean" /> </declare-styleable> + + <declare-styleable name="IlluminationDrawable"> + <attr name="highlight" format="integer" /> + <attr name="cornerRadius" format="dimension" /> + <attr name="rippleMinSize" format="dimension" /> + <attr name="rippleMaxSize" format="dimension" /> + </declare-styleable> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 82eda311da6a..b6776005d83e 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -200,6 +200,7 @@ <color name="global_screenshot_button_icon">@color/GM2_blue_500</color> <color name="global_screenshot_dismiss_background">#FFFFFF</color> <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color> + <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black --> <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index d2f64f930465..14075743ce34 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -530,6 +530,10 @@ <!-- ID for the camera that needs extra protection --> <string translatable="false" name="config_protectedCameraId"></string> + <!-- Comma-separated list of packages to exclude from camera protection e.g. + "com.android.systemui,com.android.xyz" --> + <string translatable="false" name="config_cameraProtectionExcludedPackages"></string> + <!-- Flag to turn on the rendering of the above path or not --> <bool name="config_enableDisplayCutoutProtection">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2cbb49801aa8..99e347eb1a69 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -306,6 +306,7 @@ <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_protection_height">400dp</dimen> <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_bg_protection_height">242dp</dimen> <dimen name="screenshot_preview_elevation">6dp</dimen> <dimen name="screenshot_offset_y">48dp</dimen> <dimen name="screenshot_offset_x">16dp</dimen> @@ -1168,7 +1169,7 @@ <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded --> <dimen name="bubble_expanded_default_height">180dp</dimen> <!-- Default height of bubble overflow --> - <dimen name="bubble_overflow_height">460dp</dimen> + <dimen name="bubble_overflow_height">480dp</dimen> <!-- Bubble overflow padding when there are no bubbles --> <dimen name="bubble_overflow_empty_state_padding">16dp</dimen> <!-- Padding of container for overflow bubbles --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 96843f187f72..023c77d5db14 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1253,6 +1253,9 @@ <!-- The text for the notification history link. [CHAR LIMIT=40] --> <string name="manage_notifications_history_text">History</string> + <!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] --> + <string name="notification_section_header_incoming">Incoming</string> + <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] --> <string name="notification_section_header_gentle">Silent notifications</string> @@ -2608,6 +2611,10 @@ <!-- Text used for content description of settings button in the header of expanded bubble view. [CHAR_LIMIT=NONE] --> <string name="bubbles_settings_button_description">Settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubbles</string> + <!-- Content description for button that shows bubble overflow on click [CHAR LIMIT=NONE] --> + <string name="bubble_overflow_button_content_description">Overflow</string> + <!-- Action to add overflow bubble back to stack. [CHAR LIMIT=NONE] --> + <string name="bubble_accessibility_action_add_back">Add back to stack</string> <!-- The text for the manage bubbles link. [CHAR LIMIT=NONE] --> <string name="manage_bubbles_text">Manage</string> <!-- Content description when a bubble is focused. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b5e7f4bc061e..f0edd6388ae7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -620,6 +620,10 @@ <item name="rotateButtonScaleX">-1</item> </style> + <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small"> + <item name="android:background">@null</item> + </style> + <!-- Used to style charging animation AVD animation --> <style name="ChargingAnim" /> diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt index 284074e76ae2..3015710e8a98 100644 --- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt +++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt @@ -37,20 +37,22 @@ class CameraAvailabilityListener( private val cameraManager: CameraManager, private val cutoutProtectionPath: Path, private val targetCameraId: String, + excludedPackages: String, private val executor: Executor ) { private var cutoutBounds = Rect() + private val excludedPackageIds: Set<String> private val listeners = mutableListOf<CameraTransitionCallback>() private val availabilityCallback: CameraManager.AvailabilityCallback = object : CameraManager.AvailabilityCallback() { - override fun onCameraAvailable(cameraId: String) { + override fun onCameraClosed(cameraId: String) { if (targetCameraId == cameraId) { notifyCameraInactive() } } - override fun onCameraUnavailable(cameraId: String) { - if (targetCameraId == cameraId) { + override fun onCameraOpened(cameraId: String, packageId: String) { + if (targetCameraId == cameraId && !isExcluded(packageId)) { notifyCameraActive() } } @@ -64,6 +66,7 @@ class CameraAvailabilityListener( computed.top.roundToInt(), computed.right.roundToInt(), computed.bottom.roundToInt()) + excludedPackageIds = excludedPackages.split(",").toSet() } /** @@ -87,6 +90,10 @@ class CameraAvailabilityListener( listeners.remove(callback) } + private fun isExcluded(packageId: String): Boolean { + return excludedPackageIds.contains(packageId) + } + private fun registerCameraListener() { cameraManager.registerAvailabilityCallback(executor, availabilityCallback) } @@ -118,9 +125,10 @@ class CameraAvailabilityListener( val res = context.resources val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection) val cameraId = res.getString(R.string.config_protectedCameraId) + val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages) return CameraAvailabilityListener( - manager, pathFromString(pathString), cameraId, executor) + manager, pathFromString(pathString), cameraId, excluded, executor) } private fun pathFromString(pathString: String): Path { @@ -135,4 +143,4 @@ class CameraAvailabilityListener( return p } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 4e84f06f51a7..3272fb7545e2 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -32,6 +32,8 @@ import com.android.internal.util.Preconditions import com.android.systemui.Dumpable import java.io.FileDescriptor import java.io.PrintWriter +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger @@ -211,7 +213,12 @@ class UserBroadcastDispatcher( */ override fun run() { if (registered.get()) { - context.unregisterReceiver(this@UserBroadcastDispatcher) + try { + context.unregisterReceiver(this@UserBroadcastDispatcher) + } catch (e: IllegalArgumentException) { + Log.e(TAG, "Trying to unregister unregistered receiver for user $userId", + IllegalStateException(e)) + } registered.set(false) } // Short interval without receiver, this can be problematic diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index 63dd801be7ca..b9825e1d21e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.bubbles import android.annotation.UserIdInt +import android.util.Log import com.android.systemui.bubbles.storage.BubblePersistentRepository import com.android.systemui.bubbles.storage.BubbleVolatileRepository import com.android.systemui.bubbles.storage.BubbleXmlEntity @@ -35,36 +36,39 @@ internal class BubbleDataRepository @Inject constructor( ) { private val ioScope = CoroutineScope(Dispatchers.IO) + private val uiScope = CoroutineScope(Dispatchers.Main) private var job: Job? = null /** * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk * asynchronously. */ - fun addBubble(@UserIdInt userId: Int, bubble: Bubble) { - volatileRepository.addBubble( - BubbleXmlEntity(userId, bubble.packageName, bubble.shortcutInfo?.id ?: return)) - persistToDisk() - } + fun addBubble(@UserIdInt userId: Int, bubble: Bubble) = addBubbles(userId, listOf(bubble)) /** * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk * asynchronously. */ fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) { - volatileRepository.addBubbles(bubbles.mapNotNull { - val shortcutId = it.shortcutInfo?.id ?: return@mapNotNull null - BubbleXmlEntity(userId, it.packageName, shortcutId) - }) - persistToDisk() + if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles") + val entities = transform(userId, bubbles).also(volatileRepository::addBubbles) + if (entities.isNotEmpty()) persistToDisk() } + /** + * Removes the bubbles from memory, then persists the snapshot to disk asynchronously. + */ fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) { - volatileRepository.removeBubbles(bubbles.mapNotNull { - val shortcutId = it.shortcutInfo?.id ?: return@mapNotNull null - BubbleXmlEntity(userId, it.packageName, shortcutId) - }) - persistToDisk() + if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles") + val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles) + if (entities.isNotEmpty()) persistToDisk() + } + + private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleXmlEntity> { + return bubbles.mapNotNull { b -> + val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null + BubbleXmlEntity(userId, b.packageName, shortcutId) + } } /** @@ -92,4 +96,19 @@ internal class BubbleDataRepository @Inject constructor( persistentRepository.persistsToDisk(volatileRepository.bubbles) } } + + /** + * Load bubbles from disk. + */ + fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { + val bubbleXmlEntities = persistentRepository.readFromDisk() + volatileRepository.addBubbles(bubbleXmlEntities) + uiScope.launch { + // TODO: transform bubbleXmlEntities into bubbles + // cb(bubbles) + } + } } + +private const val TAG = "BubbleDataRepository" +private const val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index b3c2c6d60708..baf92dc7abe7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -57,6 +57,7 @@ import android.view.Gravity; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.LinearLayout; import com.android.internal.policy.ScreenDecorationsUtils; @@ -456,6 +457,19 @@ public class BubbleExpandedView extends LinearLayout { mSettingsIcon.setContentDescription(getResources().getString( R.string.bubbles_settings_button_description, bubble.getAppName())); + mSettingsIcon.setAccessibilityDelegate( + new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + // On focus, have TalkBack say + // "Actions available. Use swipe up then right to view." + // in addition to the default "double tap to activate". + mStackView.setupLocalMenu(info); + } + }); + if (isNew) { mPendingIntent = mBubble.getBubbleIntent(); if (mPendingIntent != null || mBubble.getShortcutInfo() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java index 13669a68defa..e96bef36ba18 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java @@ -73,12 +73,13 @@ public class BubbleOverflow implements BubbleViewProvider { updateIcon(mContext, parentViewGroup); } - // TODO(b/149146374) Propagate theme change to bubbles in overflow. void updateIcon(Context context, ViewGroup parentViewGroup) { mInflater = LayoutInflater.from(context); mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button, parentViewGroup /* root */, false /* attachToRoot */); + mOverflowBtn.setContentDescription(mContext.getResources().getString( + R.string.bubble_overflow_button_content_description)); TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.colorBackgroundFloating}); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index de54c353fc85..c2ca9fad6d43 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -21,6 +21,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.app.Activity; +import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -32,6 +33,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -103,7 +105,7 @@ public class BubbleOverflowActivity extends Activity { - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding); final int viewHeight = recyclerViewHeight / rows; - mAdapter = new BubbleOverflowAdapter(mOverflowBubbles, + mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles, mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight); mRecyclerView.setAdapter(mAdapter); @@ -221,13 +223,15 @@ public class BubbleOverflowActivity extends Activity { } class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> { + private Context mContext; private Consumer<Bubble> mPromoteBubbleFromOverflow; private List<Bubble> mBubbles; private int mWidth; private int mHeight; - public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble, int width, - int height) { + public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble, + int width, int height) { + mContext = context; mBubbles = list; mPromoteBubbleFromOverflow = promoteBubble; mWidth = width; @@ -260,6 +264,32 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V mPromoteBubbleFromOverflow.accept(b); }); + final CharSequence titleCharSeq = + b.getEntry().getSbn().getNotification().extras.getCharSequence( + Notification.EXTRA_TITLE); + String titleStr = mContext.getResources().getString(R.string.notification_bubble_title); + if (titleCharSeq != null) { + titleStr = titleCharSeq.toString(); + } + vh.iconView.setContentDescription(mContext.getResources().getString( + R.string.bubble_content_description_single, titleStr, b.getAppName())); + + vh.iconView.setAccessibilityDelegate( + new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + // Talkback prompts "Double tap to add back to stack" + // instead of the default "Double tap to activate" + info.addAction( + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, + mContext.getResources().getString( + R.string.bubble_accessibility_action_add_back))); + } + }); + Bubble.FlyoutMessage message = b.getFlyoutMessage(); if (message != null && message.senderName != null) { vh.textView.setText(message.senderName); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 35ac78df29ab..2cb097f6075e 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -454,7 +454,6 @@ public class BubbleStackView extends FrameLayout // that means overflow was previously expanded. Set the selected bubble // internally without going through BubbleData (which would ignore it since it's // already selected). - mBubbleData.setShowingOverflow(true); setSelectedBubble(clickedBubble); } } else { @@ -1076,26 +1075,27 @@ public class BubbleStackView extends FrameLayout @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); + setupLocalMenu(info); + } + + void setupLocalMenu(AccessibilityNodeInfo info) { + Resources res = mContext.getResources(); - // Custom actions. + // Custom local actions. AccessibilityAction moveTopLeft = new AccessibilityAction(R.id.action_move_top_left, - getContext().getResources() - .getString(R.string.bubble_accessibility_action_move_top_left)); + res.getString(R.string.bubble_accessibility_action_move_top_left)); info.addAction(moveTopLeft); AccessibilityAction moveTopRight = new AccessibilityAction(R.id.action_move_top_right, - getContext().getResources() - .getString(R.string.bubble_accessibility_action_move_top_right)); + res.getString(R.string.bubble_accessibility_action_move_top_right)); info.addAction(moveTopRight); AccessibilityAction moveBottomLeft = new AccessibilityAction(R.id.action_move_bottom_left, - getContext().getResources() - .getString(R.string.bubble_accessibility_action_move_bottom_left)); + res.getString(R.string.bubble_accessibility_action_move_bottom_left)); info.addAction(moveBottomLeft); AccessibilityAction moveBottomRight = new AccessibilityAction(R.id.action_move_bottom_right, - getContext().getResources() - .getString(R.string.bubble_accessibility_action_move_bottom_right)); + res.getString(R.string.bubble_accessibility_action_move_bottom_right)); info.addAction(moveBottomRight); // Default actions. @@ -1343,7 +1343,10 @@ public class BubbleStackView extends FrameLayout } if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) { mBubbleData.setShowingOverflow(false); + } else { + mBubbleData.setShowingOverflow(true); } + final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; updatePointerPosition(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt index b2495c658948..149e2c4c4022 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt @@ -33,6 +33,7 @@ class BubblePersistentRepository @Inject constructor( "overflow_bubbles.xml"), "overflow-bubbles") fun persistsToDisk(bubbles: List<BubbleXmlEntity>): Boolean { + if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") synchronized(bubbleFile) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { Log.e(TAG, "Failed to save bubble file", e) @@ -41,6 +42,7 @@ class BubblePersistentRepository @Inject constructor( try { writeXml(stream, bubbles) bubbleFile.finishWrite(stream) + if (DEBUG) Log.d(TAG, "persisted ${bubbles.size} bubbles") return true } catch (e: Exception) { Log.e(TAG, "Failed to save bubble file, restoring backup", e) @@ -49,6 +51,16 @@ class BubblePersistentRepository @Inject constructor( } return false } + + fun readFromDisk(): List<BubbleXmlEntity> { + synchronized(bubbleFile) { + try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { + Log.e(TAG, "Failed to open bubble file", e) + } + return emptyList() + } + } } private const val TAG = "BubblePersistentRepository" +private const val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt index 3dba26807485..e1f675b83583 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt @@ -39,12 +39,6 @@ class BubbleVolatileRepository @Inject constructor() { get() = entities.toList() /** - * Add the bubble to memory and perform a de-duplication. In case the bubble already exists, - * the bubble will be moved to the last. - */ - fun addBubble(bubble: BubbleXmlEntity) = addBubbles(listOf(bubble)) - - /** * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists, * it will be moved to the last. */ @@ -55,7 +49,7 @@ class BubbleVolatileRepository @Inject constructor() { if (entities.size + bubbles.size >= CAPACITY) { entities.drop(entities.size + bubbles.size - CAPACITY) } - entities.addAll(bubbles.reversed()) + entities.addAll(bubbles) } @Synchronized diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index 3192f34b69fd..1e91653c6db7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -18,6 +18,10 @@ package com.android.systemui.bubbles.storage import com.android.internal.util.FastXmlSerializer import org.xmlpull.v1.XmlSerializer import java.io.IOException +import android.util.Xml +import com.android.internal.util.XmlUtils +import org.xmlpull.v1.XmlPullParser +import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets @@ -57,4 +61,35 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleXmlEntity) { } catch (e: IOException) { throw RuntimeException(e) } +} + +/** + * Reads the bubbles from xml file. + */ +fun readXml(stream: InputStream): List<BubbleXmlEntity> { + val bubbles = mutableListOf<BubbleXmlEntity>() + val parser: XmlPullParser = Xml.newPullParser() + parser.setInput(stream, StandardCharsets.UTF_8.name()) + XmlUtils.beginDocument(parser, TAG_BUBBLES) + val outerDepth = parser.depth + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + bubbles.add(readXmlEntry(parser) ?: continue) + } + return bubbles +} + +private fun readXmlEntry(parser: XmlPullParser): BubbleXmlEntity? { + while (parser.eventType != XmlPullParser.START_TAG) { parser.next() } + return BubbleXmlEntity( + parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_PACKAGE) ?: return null, + parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null + ) +} + +private fun XmlPullParser.getAttributeWithName(name: String): String? { + for (i in 0 until attributeCount) { + if (getAttributeName(i) == name) return getAttributeValue(i) + } + return null }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 10776c91df84..e1081cd5ef82 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -41,6 +41,9 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; @@ -56,8 +59,8 @@ import java.util.function.Consumer; public class DozeSensors { private static final boolean DEBUG = DozeService.DEBUG; - private static final String TAG = "DozeSensors"; + private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); private final Context mContext; private final AlarmManager mAlarmManager; @@ -79,6 +82,23 @@ public class DozeSensors { private boolean mListening; private boolean mPaused; + @VisibleForTesting + public enum DozeSensorsUiEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "User performs pickup gesture that activates the ambient display") + ACTION_AMBIENT_GESTURE_PICKUP(459); + + private final int mId; + + DozeSensorsUiEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) { @@ -416,6 +436,7 @@ public class DozeSensors { MetricsLogger.action( mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, subType); + UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP); } mRegistered = false; diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt new file mode 100644 index 000000000000..937472735bb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt @@ -0,0 +1,264 @@ +package com.android.systemui.media + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.content.res.ColorStateList +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.RadialGradient +import android.graphics.Rect +import android.graphics.Shader +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.util.MathUtils +import android.util.MathUtils.lerp +import android.view.MotionEvent +import android.view.View +import androidx.annotation.Keep +import com.android.internal.graphics.ColorUtils +import com.android.internal.graphics.ColorUtils.blendARGB +import com.android.systemui.Interpolators +import com.android.systemui.R +import org.xmlpull.v1.XmlPullParser + +private const val BACKGROUND_ANIM_DURATION = 370L +private const val RIPPLE_ANIM_DURATION = 800L +private const val RIPPLE_DOWN_PROGRESS = 0.05f +private const val RIPPLE_CANCEL_DURATION = 200L +private val GRADIENT_STOPS = floatArrayOf(0.2f, 1f) + +private data class RippleData( + var x: Float, + var y: Float, + var alpha: Float, + var progress: Float, + var minSize: Float, + var maxSize: Float, + var highlight: Float +) + +/** + * Drawable that can draw an animated gradient when tapped. + */ +@Keep +class IlluminationDrawable : Drawable() { + + private var cornerRadius = 0f + private var highlightColor = Color.TRANSPARENT + private val rippleData = RippleData(0f, 0f, 0f, 0f, 0f, 0f, 0f) + private var tmpHsl = floatArrayOf(0f, 0f, 0f) + private var paint = Paint() + + private var backgroundColor = Color.TRANSPARENT + set(value) { + if (value == field) { + return + } + field = value + animateBackground() + } + + /** + * Draw a small highlight under the finger before expanding (or cancelling) it. + */ + private var pressed: Boolean = false + set(value) { + if (value == field) { + return + } + field = value + + if (value) { + rippleAnimation?.cancel() + rippleData.alpha = 1f + rippleData.progress = RIPPLE_DOWN_PROGRESS + } else { + rippleAnimation?.cancel() + rippleAnimation = ValueAnimator.ofFloat(rippleData.alpha, 0f).apply { + duration = RIPPLE_CANCEL_DURATION + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.alpha = it.animatedValue as Float + invalidateSelf() + } + addListener(object : AnimatorListenerAdapter() { + var cancelled = false + override fun onAnimationCancel(animation: Animator?) { + cancelled = true; + } + + override fun onAnimationEnd(animation: Animator?) { + if (cancelled) { + return + } + rippleData.progress = 0f + rippleData.alpha = 0f + rippleAnimation = null + invalidateSelf() + } + }) + start() + } + } + invalidateSelf() + } + + private var rippleAnimation: Animator? = null + private var backgroundAnimation: ValueAnimator? = null + + /** + * Draw background and gradient. + */ + override fun draw(canvas: Canvas) { + paint.shader = if (rippleData.progress > 0) { + val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress) + val centerColor = blendARGB(paint.color, highlightColor, rippleData.alpha) + RadialGradient(rippleData.x, rippleData.y, radius, intArrayOf(centerColor, paint.color), + GRADIENT_STOPS, Shader.TileMode.CLAMP) + } else { + null + } + canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(), + cornerRadius, cornerRadius, paint) + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSPARENT + } + + override fun inflate( + r: Resources, + parser: XmlPullParser, + attrs: AttributeSet, + theme: Resources.Theme? + ) { + val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable) + cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius) + rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f) + rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f) + rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f + a.recycle() + } + + override fun setColorFilter(p0: ColorFilter?) { + throw UnsupportedOperationException("Color filters are not supported") + } + + override fun setAlpha(value: Int) { + throw UnsupportedOperationException("Alpha is not supported") + } + + /** + * Cross fade background. + * @see setTintList + * @see backgroundColor + */ + private fun animateBackground() { + ColorUtils.colorToHSL(backgroundColor, tmpHsl) + val L = tmpHsl[2] + tmpHsl[2] = MathUtils.constrain(if (L < 1f - rippleData.highlight) { + L + rippleData.highlight + } else { + L - rippleData.highlight + }, 0f, 1f) + + val initialBackground = paint.color + val initialHighlight = highlightColor + val finalHighlight = ColorUtils.HSLToColor(tmpHsl) + + backgroundAnimation?.cancel() + backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply { + duration = BACKGROUND_ANIM_DURATION + interpolator = Interpolators.FAST_OUT_LINEAR_IN + addUpdateListener { + val progress = it.animatedValue as Float + paint.color = blendARGB(initialBackground, backgroundColor, progress) + highlightColor = blendARGB(initialHighlight, finalHighlight, progress) + invalidateSelf() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + backgroundAnimation = null + } + }) + start() + } + } + + override fun setTintList(tint: ColorStateList?) { + super.setTintList(tint) + backgroundColor = tint!!.defaultColor + } + + /** + * Draws an animated ripple that expands fading away. + */ + private fun illuminate() { + rippleData.alpha = 1f + invalidateSelf() + + rippleAnimation?.cancel() + rippleAnimation = AnimatorSet().apply { + playTogether(ValueAnimator.ofFloat(1f, 0f).apply { + startDelay = 133 + duration = RIPPLE_ANIM_DURATION - startDelay + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.alpha = it.animatedValue as Float + invalidateSelf() + } + }, ValueAnimator.ofFloat(rippleData.progress, 1f).apply { + duration = RIPPLE_ANIM_DURATION + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.progress = it.animatedValue as Float + invalidateSelf() + } + }) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + rippleData.progress = 0f + rippleAnimation = null + invalidateSelf() + } + }) + start() + } + } + + /** + * Setup touch events on a view such as tapping it would trigger effects on this drawable. + * @param target View receiving touched. + * @param container View that holds this drawable. + */ + fun setupTouch(target: View, container: View) { + val containerRect = Rect() + target.setOnTouchListener { view: View, event: MotionEvent -> + container.getGlobalVisibleRect(containerRect) + rippleData.x = event.rawX - containerRect.left + rippleData.y = event.rawY - containerRect.top + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + pressed = true + } + MotionEvent.ACTION_MOVE -> { + invalidateSelf() + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + pressed = false + if (event.action == MotionEvent.ACTION_UP) { + illuminate() + } + } + } + false + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index f322489b8dc2..f72de11a01ed 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -376,7 +376,8 @@ public class PipAnimationController { // NOTE: intentionally does not apply the transaction here. // this end transaction should get executed synchronously with the final // WindowContainerTransaction in task organizer - getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()); + getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()) + .crop(tx, leash, getDestinationBounds()); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 8d6ce4718aef..7e2efc04ea8e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -552,6 +552,9 @@ public class PipTaskOrganizer extends TaskOrganizer { ? null : destinationBounds; // As for the final windowing mode, simply reset it to undefined. wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); + if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) { + wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */); + } } else { taskBounds = destinationBounds; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index e76cd5116818..174441bdf065 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -22,6 +22,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.ColorStateList; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaDescription; @@ -38,6 +39,7 @@ import android.widget.TextView; import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.R; +import com.android.systemui.media.IlluminationDrawable; import com.android.systemui.media.MediaControlPanel; import com.android.systemui.media.SeekBarObserver; import com.android.systemui.media.SeekBarViewModel; @@ -173,7 +175,7 @@ public class QSMediaPlayer extends MediaControlPanel { LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; int i = 0; for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) { - ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]); + final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]); ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]); if (thatBtn == null || thatBtn.getDrawable() == null || thatBtn.getVisibility() != View.VISIBLE) { @@ -181,6 +183,11 @@ public class QSMediaPlayer extends MediaControlPanel { continue; } + if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) { + ((IlluminationDrawable) mMediaNotifView.getBackground()) + .setupTouch(thisBtn, mMediaNotifView); + } + Drawable thatIcon = thatBtn.getDrawable(); thisBtn.setImageDrawable(thatIcon.mutate()); thisBtn.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index f77ff8cd7949..5cb75e60e22a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -28,6 +28,7 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import com.android.systemui.R; +import com.android.systemui.media.IlluminationDrawable; import com.android.systemui.media.MediaControlPanel; import com.android.systemui.plugins.ActivityStarter; @@ -104,6 +105,11 @@ public class QuickQSMediaPlayer extends MediaControlPanel { continue; } + if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) { + ((IlluminationDrawable) mMediaNotifView.getBackground()) + .setupTouch(thisBtn, mMediaNotifView); + } + Drawable thatIcon = thatBtn.getDrawable(); thisBtn.setImageDrawable(thatIcon.mutate()); thisBtn.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index e67b3d715c84..02a7aca38abe 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -813,4 +813,12 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, updateVisibility(true /* visible */); } } + + /** @return the container token for the secondary split root task. */ + public WindowContainerToken getSecondaryRoot() { + if (mSplits == null || mSplits.mSecondary == null) { + return null; + } + return mSplits.mSecondary.token; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 55a20fae4ffd..040dbe320711 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -288,7 +288,7 @@ public class InstantAppNotifier extends SystemUI mContext, 0, new Intent(Intent.ACTION_VIEW).setData(Uri.parse(helpUrl)), - 0, + PendingIntent.FLAG_IMMUTABLE, null, user) : null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 9738bcc69279..c78370ec4643 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -27,20 +27,17 @@ import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT - +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket import com.android.systemui.statusbar.phone.NotificationGroupManager import com.android.systemui.statusbar.policy.HeadsUpManager import dagger.Lazy -import java.util.Objects; +import java.util.Objects import javax.inject.Inject -import kotlin.Comparator private const val TAG = "NotifRankingManager" @@ -140,33 +137,36 @@ open class NotificationRankingManager @Inject constructor( .filterNot(notifFilter::shouldFilterOut) .sortedWith(rankingComparator) .toList() - for (entry in filtered) { - assignBucketForEntry(entry) - } + assignBuckets(filtered) return filtered } - private fun assignBucketForEntry(entry: NotificationEntry) { + private fun assignBuckets(entries: List<NotificationEntry>) { + entries.forEach { it.bucket = getBucketForEntry(it) } + if (!usePeopleFiltering) { + // If we don't have a Conversation section, just assign buckets normally based on the + // content. + return + } + // If HUNs are not continuous with the top section, break out into a new Incoming section. + entries.asReversed().asSequence().zipWithNext().forEach { (next, entry) -> + if (entry.isRowHeadsUp && entry.bucket > next.bucket) { + entry.bucket = BUCKET_HEADS_UP + } + } + } + + @PriorityBucket + private fun getBucketForEntry(entry: NotificationEntry): Int { val isHeadsUp = entry.isRowHeadsUp val isMedia = isImportantMedia(entry) val isSystemMax = entry.isSystemMax() - setBucket(entry, isHeadsUp, isMedia, isSystemMax) - } - - private fun setBucket( - entry: NotificationEntry, - isHeadsUp: Boolean, - isMedia: Boolean, - isSystemMax: Boolean - ) { - if (usePeopleFiltering && isHeadsUp) { - entry.bucket = BUCKET_HEADS_UP - } else if (usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON) { - entry.bucket = BUCKET_PEOPLE - } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) { - entry.bucket = BUCKET_ALERTING - } else { - entry.bucket = BUCKET_SILENT + return when { + usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON -> + BUCKET_PEOPLE + isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> + BUCKET_ALERTING + else -> BUCKET_SILENT } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt index 1c1b2bb087f0..a0f9dc91ce68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt @@ -28,10 +28,9 @@ import javax.inject.Singleton @Singleton class TargetSdkResolver @Inject constructor( - private val context: Context, - private val collection: CommonNotifCollection + private val context: Context ) { - init { + fun initialize(collection: CommonNotifCollection) { collection.addCollectionListener(object : NotifCollectionListener { override fun onEntryBind(entry: NotificationEntry, sbn: StatusBarNotification) { entry.targetSdk = resolveNotificationSdk(sbn) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index c9754048e1d1..6e4fcd5f97b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -25,8 +25,10 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.NotificationClicker import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController +import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.collection.TargetSdkResolver import com.android.systemui.statusbar.notification.interruption.HeadsUpController import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -56,6 +58,8 @@ class NotificationsControllerImpl @Inject constructor( private val featureFlags: FeatureFlags, private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, + private val notifPipeline: Lazy<NotifPipeline>, + private val targetSdkResolver: TargetSdkResolver, private val newNotifPipeline: Lazy<NotifPipelineInitializer>, private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, @@ -102,8 +106,10 @@ class NotificationsControllerImpl @Inject constructor( } if (featureFlags.isNewNotifPipelineRenderingEnabled) { + targetSdkResolver.initialize(notifPipeline.get()) // TODO } else { + targetSdkResolver.initialize(entryManager) remoteInputUriController.attach(entryManager) groupAlertTransferHelper.bind(entryManager, groupManager) headsUpManager.addListener(groupManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java index 29447caa1240..cc917dda2a80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row; import android.annotation.MainThread; import android.util.ArrayMap; +import android.util.Log; import androidx.annotation.NonNull; @@ -66,8 +67,10 @@ public abstract class BindStage<Params> extends BindRequester { public final Params getStageParams(@NonNull NotificationEntry entry) { Params params = mContentParams.get(entry); if (params == null) { - throw new IllegalStateException( - String.format("Entry does not have any stage parameters. key: %s", + // TODO: This should throw an exception but there are some cases of re-entrant calls + // in NotificationEntryManager (e.g. b/155324756) that cause removal in update that + // lead to inflation after the notification is "removed". + Log.wtf(TAG, String.format("Entry does not have any stage parameters. key: %s", entry.getKey())); } return params; @@ -92,6 +95,8 @@ public abstract class BindStage<Params> extends BindRequester { */ protected abstract Params newStageParams(); + private static final String TAG = "BindStage"; + /** * Interface for callback. */ 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 2e5af9a7414a..f7ad50edb2f6 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 @@ -2366,6 +2366,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateChildrenVisibility(); applyChildrenRoundness(); } + + protected void expandNotification() { + mExpandClickListener.onClick(this); + } + /** * Returns the number of channels covered by the notification row (including its children if * it's a summary notification). 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 f8d9c4648ac9..7a6109d2ce78 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 @@ -130,7 +130,13 @@ public class ExpandableNotificationRowController { mView.setOnDismissRunnable(mOnDismissRunnable); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { - mView.setLongPressListener(mNotificationGutsManager::openGuts); + mView.setLongPressListener((v, x, y, item) -> { + if (mView.isSummaryWithChildren()) { + mView.expandNotification(); + return true; + } + return mNotificationGutsManager.openGuts(v, x, y, item); + }); } if (ENABLE_REMOTE_INPUT) { mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index e4e3ebcf7671..3ee267362bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -115,6 +115,9 @@ public final class NotifBindPipeline { mLogger.logManagedRow(entry.getKey()); final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry == null) { + return; + } bindEntry.row = row; if (bindEntry.invalidated) { requestPipelineRun(entry); @@ -223,11 +226,6 @@ public final class NotifBindPipeline { private @NonNull BindEntry getBindEntry(NotificationEntry entry) { final BindEntry bindEntry = mBindEntries.get(entry); - if (bindEntry == null) { - throw new IllegalStateException( - String.format("Attempting bind on an inactive notification. key: %s", - entry.getKey())); - } return bindEntry; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index d02037cf61fd..6eec1ca33e14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -109,6 +109,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener; private SectionHeaderView mAlertingHeader; + private SectionHeaderView mIncomingHeader; private PeopleHubView mPeopleHubView; private boolean mPeopleHubVisible = false; @@ -199,6 +200,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary); } + mIncomingHeader = reinflateView( + mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header); + mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming); + mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick); + if (mMediaControlsView != null) { mKeyguardMediaPlayer.unbindView(); } @@ -218,6 +224,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section || view == mMediaControlsView || view == mPeopleHubView || view == mAlertingHeader + || view == mIncomingHeader || !Objects.equals(getBucket(view), getBucket(previous)); } @@ -229,6 +236,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section private Integer getBucket(View view) { if (view == mGentleHeader) { return BUCKET_SILENT; + } else if (view == mIncomingHeader) { + return BUCKET_HEADS_UP; } else if (view == mMediaControlsView) { return BUCKET_MEDIA_CONTROLS; } else if (view == mPeopleHubView) { @@ -267,6 +276,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section // Currently, just putting media controls in the front and incrementing the position based // on the number of heads-up notifs. int mediaControlsTarget = isKeyguard && usingMediaControls ? 0 : -1; + int currentIncomingHeaderIdx = -1; + int incomingHeaderTarget = -1; int currentPeopleHeaderIdx = -1; int peopleHeaderTarget = -1; int currentAlertingHeaderIdx = -1; @@ -281,6 +292,10 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section View child = mParent.getChildAt(i); // Track the existing positions of the headers + if (child == mIncomingHeader) { + currentIncomingHeaderIdx = i; + continue; + } if (child == mMediaControlsView) { currentMediaControlsIdx = i; continue; @@ -306,6 +321,26 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section // Once we enter a new section, calculate the target position for the header. switch (row.getEntry().getBucket()) { case BUCKET_HEADS_UP: + if (showHeaders && incomingHeaderTarget == -1) { + incomingHeaderTarget = i; + // Offset the target if there are other headers before this that will be + // moved. + if (currentIncomingHeaderIdx != -1) { + incomingHeaderTarget--; + } + if (currentMediaControlsIdx != -1) { + incomingHeaderTarget--; + } + if (currentPeopleHeaderIdx != -1) { + incomingHeaderTarget--; + } + if (currentAlertingHeaderIdx != -1) { + incomingHeaderTarget--; + } + if (currentGentleHeaderIdx != -1) { + incomingHeaderTarget--; + } + } if (mediaControlsTarget != -1) { mediaControlsTarget++; } @@ -378,8 +413,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx); adjustHeaderVisibilityAndPosition( peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx); - adjustViewPosition( - mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx); + adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx); + adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx); // Update headers to reflect state of section contents mGentleHeader.setAreThereDismissableGentleNotifs( 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 7093dd8b39e3..7f32c004808a 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 @@ -54,6 +54,7 @@ import android.graphics.Rect; import android.os.AsyncTask; import android.os.Bundle; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -756,8 +757,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd boolean showFooterView = (showDismissView || hasActiveNotifications()) && mStatusBarState != StatusBarState.KEYGUARD && !mRemoteInputManager.getController().isRemoteInputActive(); - boolean showHistory = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1; + boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; updateFooterView(showFooterView, showDismissView, showHistory); } @@ -5730,8 +5731,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd R.layout.status_bar_no_notifications, this, false); view.setText(R.string.empty_shade_text); view.setOnClickListener(v -> { - final boolean showHistory = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1; + final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; Intent intent = showHistory ? new Intent( Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( Settings.ACTION_NOTIFICATION_SETTINGS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index fc6a02840891..567ddb680848 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -321,7 +321,8 @@ public class NotificationShadeWindowController implements Callback, Dumpable, || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing || state.mHeadsUpShowing || state.mScrimsVisibility != ScrimController.TRANSPARENT) - || state.mBackgroundBlurRadius > 0; + || state.mBackgroundBlurRadius > 0 + || state.mLaunchingActivity; } private void applyFitsSystemWindows(State state) { @@ -485,6 +486,11 @@ public class NotificationShadeWindowController implements Callback, Dumpable, apply(mCurrentState); } + void setLaunchingActivity(boolean launching) { + mCurrentState.mLaunchingActivity = launching; + apply(mCurrentState); + } + public void setScrimsVisibility(int scrimsVisibility) { mCurrentState.mScrimsVisibility = scrimsVisibility; apply(mCurrentState); @@ -645,6 +651,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, boolean mForceCollapsed; boolean mForceDozeBrightness; boolean mForceUserActivity; + boolean mLaunchingActivity; boolean mBackdropShowing; boolean mWallpaperSupportsAmbientMode; boolean mNotTouchable; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 596a607bb8ad..0d2589847bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -93,6 +93,7 @@ public class NotificationShadeWindowViewController { private PhoneStatusBarView mStatusBarView; private PhoneStatusBarTransitions mBarTransitions; private StatusBar mService; + private NotificationShadeWindowController mNotificationShadeWindowController; private DragDownHelper mDragDownHelper; private boolean mDoubleTapEnabled; private boolean mSingleTapEnabled; @@ -430,10 +431,14 @@ public class NotificationShadeWindowViewController { public void setExpandAnimationPending(boolean pending) { mExpandAnimationPending = pending; + mNotificationShadeWindowController + .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning); } public void setExpandAnimationRunning(boolean running) { mExpandAnimationRunning = running; + mNotificationShadeWindowController + .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning); } public void cancelExpandHelper() { @@ -456,8 +461,9 @@ public class NotificationShadeWindowViewController { } } - public void setService(StatusBar statusBar) { + public void setService(StatusBar statusBar, NotificationShadeWindowController controller) { mService = statusBar; + mNotificationShadeWindowController = controller; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index c5901398a54b..33997b9a5735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -440,24 +440,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo if (!(relevantState && mExpansionAffectsAlpha)) { return; } - applyExpansionToAlpha(); - if (mUpdatePending) { - return; - } - setOrAdaptCurrentAnimation(mScrimBehind); - setOrAdaptCurrentAnimation(mScrimInFront); - setOrAdaptCurrentAnimation(mScrimForBubble); - dispatchScrimState(mScrimBehind.getViewAlpha()); - - // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING - // and docking. - if (mWallpaperVisibilityTimedOut) { - mWallpaperVisibilityTimedOut = false; - DejankUtils.postAfterTraversal(() -> { - mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), - AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); - }); - } + applyAndDispatchExpansion(); } } @@ -513,6 +496,27 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } } + private void applyAndDispatchExpansion() { + applyExpansionToAlpha(); + if (mUpdatePending) { + return; + } + setOrAdaptCurrentAnimation(mScrimBehind); + setOrAdaptCurrentAnimation(mScrimInFront); + setOrAdaptCurrentAnimation(mScrimForBubble); + dispatchScrimState(mScrimBehind.getViewAlpha()); + + // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING + // and docking. + if (mWallpaperVisibilityTimedOut) { + mWallpaperVisibilityTimedOut = false; + DejankUtils.postAfterTraversal(() -> { + mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), + AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + }); + } + } + /** * Sets the given drawable as the background of the scrim that shows up behind the * notifications. @@ -1006,6 +1010,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { mExpansionAffectsAlpha = expansionAffectsAlpha; + if (expansionAffectsAlpha) { + applyAndDispatchExpansion(); + } } public void setKeyguardOccluded(boolean keyguardOccluded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index bbf83bc2057a..dd54a3d800fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1001,7 +1001,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateTheme(); inflateStatusBarWindow(); - mNotificationShadeWindowViewController.setService(this); + mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController); mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener()); // TODO: Deal with the ugliness that comes from having some of the statusbar broken out diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index d40b5f9728dd..1df617d68374 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -483,7 +483,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit if (showHistory) { tsb.addNextIntent(intent); } - tsb.startActivities(); + tsb.startActivities(null, UserHandle.CURRENT); if (shouldCollapse()) { // Putting it back on the main thread, since we're touching views mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels( diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 64cac84ff24e..93f45c51f0eb 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -189,8 +189,8 @@ public class SystemWindows { public SurfaceControl getViewSurface(View rootView) { for (int i = 0; i < mPerDisplay.size(); ++i) { for (int iWm = 0; iWm < mPerDisplay.valueAt(i).mWwms.size(); ++iWm) { - SurfaceControl out = - mPerDisplay.valueAt(i).mWwms.get(iWm).getSurfaceControlForWindow(rootView); + SurfaceControl out = mPerDisplay.valueAt(i).mWwms.valueAt(iWm) + .getSurfaceControlForWindow(rootView); if (out != null) { return out; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 9d35e53e7421..128d6e5612f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -37,6 +39,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; @@ -46,6 +49,7 @@ import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.util.concurrency.DelayableExecutor; import org.junit.Before; @@ -100,8 +104,14 @@ public class QSPanelTest extends SysuiTestCase { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); + + // Dependencies for QSSecurityFooter + mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter); + mDependency.injectMockDependency(SecurityController.class); + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class)); + mUiEventLogger = new UiEventLoggerFake(); mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 72a65034922c..53ef86660867 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -23,10 +23,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -41,6 +41,7 @@ import android.content.pm.ServiceInfo; import android.provider.Settings; import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.text.TextUtils; import android.util.ArraySet; @@ -50,10 +51,6 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tiles.HotspotTile; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -63,6 +60,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -73,6 +71,7 @@ import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { private static final String CURRENT_TILES = "wifi,dnd,nfc"; private static final String ONLY_STOCK_TILES = "wifi,dnd"; @@ -99,8 +98,6 @@ public class TileQueryHelperTest extends SysuiTestCase { private QSTileHost mQSTileHost; @Mock private PackageManager mPackageManager; - @Mock - private QSLogger mQSLogger; @Captor private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; @@ -112,8 +109,8 @@ public class TileQueryHelperTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); + mContext.setMockPackageManager(mPackageManager); - when(mQSTileHost.getQSLogger()).thenReturn(mQSLogger); mState = new QSTile.State(); doAnswer(invocation -> { @@ -279,11 +276,10 @@ public class TileQueryHelperTest extends SysuiTestCase { } @Test - public void testQueryTiles_notAvailableDestroyed_isNotNullSpec() { - HotspotController mockHC = mock(HotspotController.class); - DataSaverController mockDSC = mock(DataSaverController.class); - when(mockHC.isHotspotSupported()).thenReturn(false); - HotspotTile t = new HotspotTile(mQSTileHost, mockHC, mockDSC); + public void testQueryTiles_notAvailableDestroyed_tileSpecIsSet() { + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null); + + QSTile t = mock(QSTile.class); when(mQSTileHost.createTile("hotspot")).thenReturn(t); mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, @@ -292,6 +288,8 @@ public class TileQueryHelperTest extends SysuiTestCase { mTileQueryHelper.queryTiles(mQSTileHost); FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); - verify(mQSLogger).logTileDestroyed(eq("hotspot"), anyString()); + InOrder verifier = inOrder(t); + verifier.verify(t).setTileSpec("hotspot"); + verifier.verify(t).destroy(); } } 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 1cb4d898f9d2..c4bd1b281b24 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 @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.metrics.LogMaker; +import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -152,7 +153,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NOTIFICATION_NEW_INTERRUPTION_MODEL, 0); Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); - Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED, 1); + Settings.Secure.putIntForUser(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED, + 1, UserHandle.USER_CURRENT); // Inject dependencies before initializing the layout mDependency.injectMockDependency(VisualStabilityManager.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index cc2d1c25de38..e04d25b17c71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -83,6 +83,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; @Mock private NotificationShadeDepthController mNotificationShadeDepthController; @Mock private SuperStatusBarViewFactory mStatusBarViewFactory; + @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Before public void setUp() { @@ -121,7 +122,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationPanelViewController, mStatusBarViewFactory); mController.setupExpandedStatusBar(); - mController.setService(mStatusBar); + mController.setService(mStatusBar, mNotificationShadeWindowController); mController.setDragDownHelper(mDragDownHelper); } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index bfb65241ec6d..cbc5e14139ac 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -27,7 +27,7 @@ java_defaults { "androidx.annotation_annotation", "netd_aidl_interface-V3-java", "netlink-client", - "networkstack-aidl-interfaces-unstable-java", + "networkstack-aidl-interfaces-java", "android.hardware.tetheroffload.config-V1.0-java", "android.hardware.tetheroffload.control-V1.0-java", "net-utils-framework-common", @@ -109,6 +109,7 @@ android_app { manifest: "AndroidManifest_InProcess.xml", // InProcessTethering is a replacement for Tethering overrides: ["Tethering"], + apex_available: ["com.android.tethering"], } // Updatable tethering packaged as an application diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp index 24df5f696077..20ccd2ad6453 100644 --- a/packages/Tethering/apex/Android.bp +++ b/packages/Tethering/apex/Android.bp @@ -36,3 +36,12 @@ android_app_certificate { name: "com.android.tethering.certificate", certificate: "com.android.tethering", } + +override_apex { + name: "com.android.tethering.inprocess", + base: "com.android.tethering", + package_name: "com.android.tethering.inprocess", + apps: [ + "InProcessTethering", + ], +} diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index 8ae1593949f1..d029d2bded79 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -94,6 +94,15 @@ droidstubs { "framework-module-stubs-defaults-publicapi", "framework-tethering-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-tethering.api.public.latest", + removed_api_file: ":framework-tethering-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-tethering.api.public.latest", + }, + }, } droidstubs { @@ -102,6 +111,15 @@ droidstubs { "framework-module-stubs-defaults-systemapi", "framework-tethering-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-tethering.api.system.latest", + removed_api_file: ":framework-tethering-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-tethering.api.system.latest", + }, + }, } droidstubs { @@ -110,6 +128,15 @@ droidstubs { "framework-module-api-defaults-module_libs_api", "framework-tethering-stubs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-tethering.api.module-lib.latest", + removed_api_file: ":framework-tethering-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-tethering.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 83c99d22fda7..9dda7166a293 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -64,6 +64,13 @@ <string-array translatable="false" name="config_tether_dhcp_range"> </string-array> + <!-- Used to config periodic polls tether offload stats from tethering offload HAL to make the + data warnings work. 5000(ms) by default. If the device doesn't want to poll tether + offload stats, this should be -1. Note that this setting could be override by + runtime resource overlays. + --> + <integer translatable="false" name="config_tether_offload_poll_interval">5000</integer> + <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI, WIFI} values allowable for tethering. diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml index 16ae8ade19da..4c78a74d5358 100644 --- a/packages/Tethering/res/values/overlayable.xml +++ b/packages/Tethering/res/values/overlayable.xml @@ -24,6 +24,7 @@ <item type="array" name="config_tether_bluetooth_regexs"/> <item type="array" name="config_tether_dhcp_range"/> <item type="bool" name="config_tether_enable_legacy_dhcp_server"/> + <item type="integer" name="config_tether_offload_poll_interval"/> <item type="array" name="config_tether_upstream_types"/> <item type="bool" name="config_tether_upstream_automatic"/> <!-- Configuration values for tethering entitlement check --> diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java index 1817f35f1dcd..88c77b07e7e3 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java @@ -26,6 +26,8 @@ import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.NetworkStatsManager; @@ -77,7 +79,6 @@ public class OffloadController { private static final boolean DBG = false; private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); - private static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; @VisibleForTesting enum StatsType { @@ -134,11 +135,9 @@ public class OffloadController { private final Dependencies mDeps; // TODO: Put more parameters in constructor into dependency object. - static class Dependencies { - int getPerformPollInterval() { - // TODO: Consider make this configurable. - return DEFAULT_PERFORM_POLL_INTERVAL_MS; - } + interface Dependencies { + @NonNull + TetheringConfiguration getTetherConfig(); } public OffloadController(Handler h, OffloadHardwareInterface hwi, @@ -452,12 +451,16 @@ public class OffloadController { if (mHandler.hasCallbacks(mScheduledPollingTask)) { mHandler.removeCallbacks(mScheduledPollingTask); } - mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); + mHandler.postDelayed(mScheduledPollingTask, + mDeps.getTetherConfig().getOffloadPollInterval()); } private boolean isPollingStatsNeeded() { return started() && mRemainingAlertQuota > 0 - && !TextUtils.isEmpty(currentUpstreamInterface()); + && !TextUtils.isEmpty(currentUpstreamInterface()) + && mDeps.getTetherConfig() != null + && mDeps.getTetherConfig().getOffloadPollInterval() + >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; } private boolean maybeUpdateDataLimit(String iface) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 0a95a5e0073b..b2a43c47d1c6 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -62,7 +62,6 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; -import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; @@ -268,12 +267,15 @@ public class Tethering { mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps); mTetherMasterSM.start(); - final NetworkStatsManager statsManager = - (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE); mHandler = mTetherMasterSM.getHandler(); - mOffloadController = new OffloadController(mHandler, - mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), - statsManager, mLog, new OffloadController.Dependencies()); + mOffloadController = mDeps.getOffloadController(mHandler, mLog, + new OffloadController.Dependencies() { + + @Override + public TetheringConfiguration getTetherConfig() { + return mConfig; + } + }); mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index aeac437e24e3..9d4e74732729 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -78,6 +78,12 @@ public class TetheringConfiguration { public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER = "tether_enable_legacy_dhcp_server"; + /** + * Default value that used to periodic polls tether offload stats from tethering offload HAL + * to make the data warnings work. + */ + public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000; + public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; public final String[] tetherableWifiP2pRegexs; @@ -96,6 +102,8 @@ public class TetheringConfiguration { public final int activeDataSubId; + private final int mOffloadPollInterval; + public TetheringConfiguration(Context ctx, SharedLog log, int id) { final SharedLog configLog = log.forSubComponent("config"); @@ -129,6 +137,10 @@ public class TetheringConfiguration { R.integer.config_mobile_hotspot_provision_check_period, 0 /* No periodic re-check */); + mOffloadPollInterval = getResourceInteger(res, + R.integer.config_tether_offload_poll_interval, + DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + configLog.log(toString()); } @@ -189,6 +201,9 @@ public class TetheringConfiguration { dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges); dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS); + pw.print("offloadPollInterval: "); + pw.println(mOffloadPollInterval); + dumpStringArray(pw, "provisioningApp", provisioningApp); pw.print("provisioningAppNoUi: "); pw.println(provisioningAppNoUi); @@ -208,6 +223,7 @@ public class TetheringConfiguration { makeString(tetherableBluetoothRegexs))); sj.add(String.format("isDunRequired:%s", isDunRequired)); sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically)); + sj.add(String.format("offloadPollInterval:%d", mOffloadPollInterval)); sj.add(String.format("preferredUpstreamIfaceTypes:%s", toIntArray(preferredUpstreamIfaceTypes))); sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); @@ -246,6 +262,10 @@ public class TetheringConfiguration { return (tm != null) ? tm.isTetheringApnRequired() : false; } + public int getOffloadPollInterval() { + return mOffloadPollInterval; + } + private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) { final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types); final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 9b54b5ff2403..802f2acb3310 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -16,6 +16,7 @@ package com.android.networkstack.tethering; +import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.net.INetd; @@ -47,6 +48,19 @@ public abstract class TetheringDependencies { } /** + * Get a reference to the offload controller to be used by tethering. + */ + @NonNull + public OffloadController getOffloadController(@NonNull Handler h, + @NonNull SharedLog log, @NonNull OffloadController.Dependencies deps) { + final NetworkStatsManager statsManager = + (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); + return new OffloadController(h, getOffloadHardwareInterface(h, log), + getContext().getContentResolver(), statsManager, log, deps); + } + + + /** * Get a reference to the UpstreamNetworkMonitor to be used by tethering. */ public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java index 088a663190b8..b291438937c7 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java @@ -29,15 +29,15 @@ import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE; import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID; import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats; +import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; import static com.android.testutils.MiscAssertsKt.assertContainsAll; import static com.android.testutils.MiscAssertsKt.assertThrows; -import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals; +import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals; import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; @@ -63,10 +63,10 @@ import android.net.LinkProperties; import android.net.NetworkStats; import android.net.NetworkStats.Entry; import android.net.RouteInfo; -import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProvider; import android.net.util.SharedLog; import android.os.Handler; -import android.os.Looper; +import android.os.test.TestLooper; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.test.mock.MockContentResolver; @@ -75,7 +75,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.TestableNetworkStatsProviderCbBinder; import org.junit.After; import org.junit.Before; @@ -109,17 +109,20 @@ public class OffloadControllerTest { @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; @Mock private NetworkStatsManager mStatsManager; - @Mock private INetworkStatsProviderCallback mTetherStatsProviderCb; + @Mock private TetheringConfiguration mTetherConfig; + // Late init since methods must be called by the thread that created this object. + private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; private OffloadController.OffloadTetheringStatsProvider mTetherStatsProvider; private final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor = ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class); private MockContentResolver mContentResolver; + private final TestLooper mTestLooper = new TestLooper(); private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() { @Override - int getPerformPollInterval() { - return 0; + public TetheringConfiguration getTetherConfig() { + return mTetherConfig; } }; @@ -131,6 +134,7 @@ public class OffloadControllerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolver); FakeSettingsProvider.clearSettingsProvider(); + when(mTetherConfig.getOffloadPollInterval()).thenReturn(-1); // Disabled. } @After public void tearDown() throws Exception { @@ -150,12 +154,16 @@ public class OffloadControllerTest { Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); } + private void setOffloadPollInterval(int interval) { + when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval); + } + private void waitForIdle() { - HandlerUtilsKt.waitForIdle(new Handler(Looper.getMainLooper()), WAIT_FOR_IDLE_TIMEOUT); + mTestLooper.dispatchAll(); } private OffloadController makeOffloadController() throws Exception { - OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), + OffloadController offload = new OffloadController(new Handler(mTestLooper.getLooper()), mHardware, mContentResolver, mStatsManager, new SharedLog("test"), mDeps); final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider> tetherStatsProviderCaptor = @@ -164,6 +172,7 @@ public class OffloadControllerTest { tetherStatsProviderCaptor.capture()); mTetherStatsProvider = tetherStatsProviderCaptor.getValue(); assertNotNull(mTetherStatsProvider); + mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder(); mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb); return offload; } @@ -352,9 +361,9 @@ public class OffloadControllerTest { stacked.setInterfaceName("stacked"); stacked.addLinkAddress(new LinkAddress("192.0.2.129/25")); stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null, - RTN_UNICAST)); + RTN_UNICAST)); stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null, - RTN_UNICAST)); + RTN_UNICAST)); assertTrue(lp.addStackedLink(stacked)); offload.setUpstreamLinkProperties(lp); // No change in local addresses means no call to setLocalPrefixes(). @@ -459,20 +468,12 @@ public class OffloadControllerTest { .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999)) .addEntry(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321)); - assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats)); - assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats)); - - final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass( - NetworkStats.class); - final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass( - NetworkStats.class); + assertNetworkStatsEquals(expectedIfaceStats, ifaceStats); + assertNetworkStatsEquals(expectedUidStats, uidStats); // Force pushing stats update to verify the stats reported. mTetherStatsProvider.pushTetherStats(); - verify(mTetherStatsProviderCb, times(1)) - .notifyStatsUpdated(anyInt(), ifaceStatsCaptor.capture(), uidStatsCaptor.capture()); - assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue())); - assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue())); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats); when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn( new ForwardedStats(100000, 100000)); @@ -498,11 +499,10 @@ public class OffloadControllerTest { .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999)) .addEntry(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321)); - assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu)); - assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu)); + assertNetworkStatsEquals(expectedIfaceStatsAccu, ifaceStatsAccu); + assertNetworkStatsEquals(expectedUidStatsAccu, uidStatsAccu); // Verify that only diff of stats is reported. - reset(mTetherStatsProviderCb); mTetherStatsProvider.pushTetherStats(); final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0)) @@ -511,10 +511,8 @@ public class OffloadControllerTest { final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0)) .addEntry(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000)); - verify(mTetherStatsProviderCb, times(1)) - .notifyStatsUpdated(anyInt(), ifaceStatsCaptor.capture(), uidStatsCaptor.capture()); - assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue())); - assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue())); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff, + expectedUidStatsDiff); } @Test @@ -591,7 +589,7 @@ public class OffloadControllerTest { OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedLimitReached(); - verify(mTetherStatsProviderCb, times(1)).notifyStatsUpdated(anyInt(), any(), any()); + mTetherStatsProviderCb.expectNotifyStatsUpdated(); } @Test @@ -695,8 +693,8 @@ public class OffloadControllerTest { verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); // TODO: verify the exact stats reported. - verify(mTetherStatsProviderCb, times(1)).notifyStatsUpdated(anyInt(), any(), any()); - verifyNoMoreInteractions(mTetherStatsProviderCb); + mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.assertNoCallback(); verifyNoMoreInteractions(mHardware); } @@ -760,8 +758,8 @@ public class OffloadControllerTest { // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); - verify(mTetherStatsProviderCb, times(1)).notifyStatsUpdated(anyInt(), any(), any()); - verifyNoMoreInteractions(mTetherStatsProviderCb); + mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.assertNoCallback(); // TODO: verify local prefixes and downstreams are also pushed to the HAL. verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture()); @@ -780,4 +778,50 @@ public class OffloadControllerTest { verifyNoMoreInteractions(mHardware); } + @Test + public void testOnSetAlert() throws Exception { + setupFunctioningHardwareInterface(); + enableOffload(); + setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + final OffloadController offload = makeOffloadController(); + offload.start(); + + // Initialize with fake eth upstream. + final String ethernetIface = "eth1"; + InOrder inOrder = inOrder(mHardware); + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(ethernetIface); + offload.setUpstreamLinkProperties(lp); + // Previous upstream was null, so no stats are fetched. + inOrder.verify(mHardware, never()).getForwardedStats(any()); + + // Verify that set quota to 0 will immediately triggers an callback. + mTetherStatsProvider.onSetAlert(0); + waitForIdle(); + mTetherStatsProviderCb.expectNotifyAlertReached(); + + // Verify that notifyAlertReached never fired if quota is not yet reached. + when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn( + new ForwardedStats(0, 0)); + mTetherStatsProvider.onSetAlert(100); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.assertNoCallback(); + + // Verify that notifyAlertReached fired when quota is reached. + when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn( + new ForwardedStats(50, 50)); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.expectNotifyAlertReached(); + + // Verify that set quota with UNLIMITED won't trigger any callback, and won't fetch + // any stats since the polling is stopped. + reset(mHardware); + mTetherStatsProvider.onSetAlert(NetworkStatsProvider.QUOTA_UNLIMITED); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.assertNoCallback(); + verify(mHardware, never()).getForwardedStats(any()); + } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java index 07ddea43f4e8..e8ba5b8168d7 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java @@ -115,6 +115,8 @@ public class TetheringConfigurationTest { when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn( new String[0]); + when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn( + TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) .thenReturn(new String[]{ "test_wlan\\d" }); @@ -314,6 +316,23 @@ public class TetheringConfigurationTest { } @Test + public void testOffloadIntervalByResource() { + final TetheringConfiguration intervalByDefault = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertEquals(TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, + intervalByDefault.getOffloadPollInterval()); + + final int[] testOverrides = {0, 3000, -1}; + for (final int override : testOverrides) { + when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn( + override); + final TetheringConfiguration overrideByRes = + new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertEquals(override, overrideByRes.getOffloadPollInterval()); + } + } + + @Test public void testGetResourcesBySubId() { setUpResourceForSubId(); final TetheringConfiguration cfg = new TetheringConfiguration( diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 0363f5f9989f..fff7a70f54d0 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -150,6 +150,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.util.ArrayList; @@ -212,6 +214,9 @@ public class TetheringTest { private Tethering mTethering; private PhoneStateListener mPhoneStateListener; private InterfaceConfigurationParcel mInterfaceConfiguration; + private TetheringConfiguration mConfig; + private EntitlementManager mEntitleMgr; + private OffloadController mOffloadCtrl; private class TestContext extends BroadcastInterceptingContext { TestContext(Context base) { @@ -297,8 +302,9 @@ public class TetheringTest { } } - private class MockTetheringConfiguration extends TetheringConfiguration { - MockTetheringConfiguration(Context ctx, SharedLog log, int id) { + // MyTetheringConfiguration is used to override static method for testing. + private class MyTetheringConfiguration extends TetheringConfiguration { + MyTetheringConfiguration(Context ctx, SharedLog log, int id) { super(ctx, log, id); } @@ -328,6 +334,15 @@ public class TetheringTest { } @Override + public OffloadController getOffloadController(Handler h, SharedLog log, + OffloadController.Dependencies deps) { + mOffloadCtrl = spy(super.getOffloadController(h, log, deps)); + // Return real object here instead of mock because + // testReportFailCallbackIfOffloadNotSupported depend on real OffloadController object. + return mOffloadCtrl; + } + + @Override public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, SharedLog log, int what) { mUpstreamNetworkMonitorMasterSM = target; @@ -352,6 +367,13 @@ public class TetheringTest { } @Override + public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, + SharedLog log, int what) { + mEntitleMgr = spy(super.getEntitlementManager(ctx, target, log, what)); + return mEntitleMgr; + } + + @Override public boolean isTetheringSupported() { return true; } @@ -359,7 +381,8 @@ public class TetheringTest { @Override public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log, int subId) { - return new MockTetheringConfiguration(ctx, log, subId); + mConfig = spy(new MyTetheringConfiguration(ctx, log, subId)); + return mConfig; } @Override @@ -1726,6 +1749,17 @@ public class TetheringTest { verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any()); } + @Test + public void testDumpTetheringLog() throws Exception { + final FileDescriptor mockFd = mock(FileDescriptor.class); + final PrintWriter mockPw = mock(PrintWriter.class); + runUsbTethering(null); + mTethering.dump(mockFd, mockPw, new String[0]); + verify(mConfig).dump(any()); + verify(mEntitleMgr).dump(any()); + verify(mOffloadCtrl).dump(any()); + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. } diff --git a/services/Android.bp b/services/Android.bp index 2e70f1c528ee..882085a7d0ba 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -80,6 +80,8 @@ java_library { "services.usb", "services.voiceinteraction", "services.wifi", + "service-blobstore", + "service-jobscheduler", "android.hidl.base-V1.0-java", ], @@ -135,7 +137,7 @@ droidstubs { }, last_released: { api_file: ":android.api.system-server.latest", - removed_api_file: "api/removed.txt", + removed_api_file: ":removed.api.system-server.latest", baseline_file: ":system-server-api-incompatibilities-with-last-released" }, api_lint: { diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 0b3899d15993..fdc5f810db22 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -120,7 +120,7 @@ public class AppPredictionPerUserService extends this::removeAppPredictionSessionInfo)); } final boolean serviceExists = resolveService(sessionId, s -> - s.onCreatePredictionSession(context, sessionId)); + s.onCreatePredictionSession(context, sessionId), true); if (!serviceExists) { mSessionInfos.remove(sessionId); } @@ -132,7 +132,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId, @NonNull AppTargetEvent event) { - resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event)); + resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event), false); } /** @@ -142,7 +142,7 @@ public class AppPredictionPerUserService extends public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId, @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { resolveService(sessionId, s -> - s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds)); + s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds), false); } /** @@ -151,7 +151,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId, @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { - resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback)); + resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback), true); } /** @@ -161,7 +161,7 @@ public class AppPredictionPerUserService extends public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { final boolean serviceExists = resolveService(sessionId, s -> - s.registerPredictionUpdates(sessionId, callback)); + s.registerPredictionUpdates(sessionId, callback), false); final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (serviceExists && sessionInfo != null) { sessionInfo.addCallbackLocked(callback); @@ -175,7 +175,7 @@ public class AppPredictionPerUserService extends public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { final boolean serviceExists = resolveService(sessionId, s -> - s.unregisterPredictionUpdates(sessionId, callback)); + s.unregisterPredictionUpdates(sessionId, callback), false); final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (serviceExists && sessionInfo != null) { sessionInfo.removeCallbackLocked(callback); @@ -187,7 +187,7 @@ public class AppPredictionPerUserService extends */ @GuardedBy("mLock") public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) { - resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId)); + resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId), true); } /** @@ -196,7 +196,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) { final boolean serviceExists = resolveService(sessionId, s -> - s.onDestroyPredictionSession(sessionId)); + s.onDestroyPredictionSession(sessionId), false); final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (serviceExists && sessionInfo != null) { sessionInfo.destroy(); @@ -304,7 +304,8 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") @Nullable protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId, - @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) { + @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb, + boolean sendImmediately) { final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); if (sessionInfo == null) return false; if (sessionInfo.mUsesPeopleService) { @@ -322,7 +323,13 @@ public class AppPredictionPerUserService extends } else { final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { - service.scheduleOnResolvedService(cb); + // TODO(b/155887722): implement a priority system so that latency-sensitive + // requests gets executed first. + if (sendImmediately) { + service.executeOnResolvedService(cb); + } else { + service.scheduleOnResolvedService(cb); + } } return service != null; } diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java index ceb1cafcebeb..a57ff11fa20f 100644 --- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -80,6 +80,13 @@ public class RemoteAppPredictionService extends } /** + * Execute async request on remote service immediately instead of sending it to Handler queue. + */ + public void executeOnResolvedService(@NonNull AsyncRequest<IPredictionService> request) { + executeAsyncRequest(request); + } + + /** * Failure callback */ public interface RemoteAppPredictionServiceCallbacks diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index ce11c76e5c6a..22451e1d992e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -37,6 +37,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.InlineSuggestionsRequestInfo; +import com.android.server.autofill.ui.InlineSuggestionFactory; import com.android.server.inputmethod.InputMethodManagerInternal; import java.lang.ref.WeakReference; @@ -242,7 +243,8 @@ final class AutofillInlineSuggestionsRequestSession { } if (sDebug) Log.d(TAG, "Send inline response: " + response.getInlineSuggestions().size()); try { - mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response); + mResponseCallback.onInlineSuggestionsResponse(mAutofillId, + InlineSuggestionFactory.copy(response)); } catch (RemoteException e) { Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME"); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java index 255adcd92da3..617c111c6c38 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java @@ -47,7 +47,7 @@ public final class RemoteInlineSuggestionRenderService extends private static final String TAG = "RemoteInlineSuggestionRenderService"; - private final int mIdleUnbindTimeoutMs = 5000; + private final long mIdleUnbindTimeoutMs = PERMANENT_BOUND_TIMEOUT_MS; RemoteInlineSuggestionRenderService(Context context, ComponentName componentName, String serviceInterface, int userId, InlineSuggestionRenderCallbacks callback, diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java b/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java new file mode 100644 index 000000000000..819f2b813a5e --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill.ui; + +import static com.android.server.autofill.Helper.sVerbose; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.util.Slog; + +import com.android.internal.view.inline.IInlineContentCallback; +import com.android.internal.view.inline.IInlineContentProvider; +import com.android.server.FgThread; + +/** + * We create one instance of this class for each {@link android.view.inputmethod.InlineSuggestion} + * instance. Each inline suggestion instance will only be sent to the remote IME process once. In + * case of filtering and resending the suggestion when keyboard state changes between hide and + * show, a new instance of this class will be created using {@link #copy()}, with the same backing + * {@link RemoteInlineSuggestionUi}. When the + * {@link #provideContent(int, int, IInlineContentCallback)} is called the first time (it's only + * allowed to be called at most once), the passed in width/height is used to determine whether + * the existing {@link RemoteInlineSuggestionUi} provided in the constructor can be reused, or a + * new one should be created to suit the new size requirement for the view. In normal cases, + * we should not expect the size requirement to change, although in theory the public API allows + * the IME to do that. + * + * <p>This design is to enable us to be able to reuse the backing remote view while still keeping + * the callbacks relatively well aligned. For example, if we allow multiple remote IME binder + * callbacks to call into one instance of this class, then binder A may call in with width/height + * X for which we create a view (i.e. {@link RemoteInlineSuggestionUi}) for it, + * + * See also {@link RemoteInlineSuggestionUi} for relevant information. + */ +public final class InlineContentProviderImpl extends IInlineContentProvider.Stub { + + // TODO(b/153615023): consider not holding strong reference to heavy objects in this stub, to + // avoid memory leak in case the client app is holding the remote reference for a longer + // time than expected. Essentially we need strong reference in the system process to + // the member variables, but weak reference to them in the IInlineContentProvider.Stub. + + private static final String TAG = InlineContentProviderImpl.class.getSimpleName(); + + private final Handler mHandler = FgThread.getHandler();; + + @NonNull + private final RemoteInlineSuggestionViewConnector mRemoteInlineSuggestionViewConnector; + @Nullable + private RemoteInlineSuggestionUi mRemoteInlineSuggestionUi; + + private boolean mProvideContentCalled = false; + + InlineContentProviderImpl( + @NonNull RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector, + @Nullable RemoteInlineSuggestionUi remoteInlineSuggestionUi) { + mRemoteInlineSuggestionViewConnector = remoteInlineSuggestionViewConnector; + mRemoteInlineSuggestionUi = remoteInlineSuggestionUi; + } + + /** + * Returns a new instance of this class, with the same {@code mInlineSuggestionRenderer} and + * {@code mRemoteInlineSuggestionUi}. The latter may or may not be reusable depending on the + * size information provided when the client calls {@link #provideContent(int, int, + * IInlineContentCallback)}. + */ + @NonNull + public InlineContentProviderImpl copy() { + return new InlineContentProviderImpl(mRemoteInlineSuggestionViewConnector, + mRemoteInlineSuggestionUi); + } + + /** + * Provides a SurfacePackage associated with the inline suggestion view to the IME. If such + * view doesn't exit, then create a new one. This method should be called once per lifecycle + * of this object. Any further calls to the method will be ignored. + */ + @Override + public void provideContent(int width, int height, IInlineContentCallback callback) { + mHandler.post(() -> handleProvideContent(width, height, callback)); + } + + @Override + public void requestSurfacePackage() { + mHandler.post(this::handleGetSurfacePackage); + } + + @Override + public void onSurfacePackageReleased() { + mHandler.post(this::handleOnSurfacePackageReleased); + } + + private void handleProvideContent(int width, int height, IInlineContentCallback callback) { + if (sVerbose) Slog.v(TAG, "handleProvideContent"); + if (mProvideContentCalled) { + // This method should only be called once. + return; + } + mProvideContentCalled = true; + if (mRemoteInlineSuggestionUi == null || !mRemoteInlineSuggestionUi.match(width, height)) { + mRemoteInlineSuggestionUi = new RemoteInlineSuggestionUi( + mRemoteInlineSuggestionViewConnector, + width, height, mHandler); + } + mRemoteInlineSuggestionUi.setInlineContentCallback(callback); + mRemoteInlineSuggestionUi.requestSurfacePackage(); + } + + private void handleGetSurfacePackage() { + if (sVerbose) Slog.v(TAG, "handleGetSurfacePackage"); + if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) { + // provideContent should be called first, and remote UI should not be null. + return; + } + mRemoteInlineSuggestionUi.requestSurfacePackage(); + } + + private void handleOnSurfacePackageReleased() { + if (sVerbose) Slog.v(TAG, "handleOnSurfacePackageReleased"); + if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) { + // provideContent should be called first, and remote UI should not be null. + return; + } + mRemoteInlineSuggestionUi.surfacePackageReleased(); + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index 79c9efa48d73..e74463a8584b 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -24,14 +24,11 @@ import android.annotation.Nullable; import android.content.Intent; import android.content.IntentSender; import android.os.IBinder; -import android.os.RemoteException; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; -import android.service.autofill.IInlineSuggestionUiCallback; import android.service.autofill.InlinePresentation; import android.text.TextUtils; import android.util.Slog; -import android.view.SurfaceControlViewHost; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -41,12 +38,8 @@ import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InlineSuggestionsResponse; import android.widget.inline.InlinePresentationSpec; -import com.android.internal.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; -import com.android.server.LocalServices; -import com.android.server.UiThread; import com.android.server.autofill.RemoteInlineSuggestionRenderService; -import com.android.server.inputmethod.InputMethodManagerInternal; import java.util.ArrayList; import java.util.List; @@ -73,6 +66,27 @@ public final class InlineSuggestionFactory { } /** + * Returns a copy of the response, that internally copies the {@link IInlineContentProvider} + * so that it's not reused by the remote IME process across different inline suggestions. + * See {@link InlineContentProviderImpl} for why this is needed. + */ + @NonNull + public static InlineSuggestionsResponse copy(@NonNull InlineSuggestionsResponse response) { + final ArrayList<InlineSuggestion> copiedInlineSuggestions = new ArrayList<>(); + for (InlineSuggestion inlineSuggestion : response.getInlineSuggestions()) { + final IInlineContentProvider contentProvider = inlineSuggestion.getContentProvider(); + if (contentProvider instanceof InlineContentProviderImpl) { + copiedInlineSuggestions.add(new + InlineSuggestion(inlineSuggestion.getInfo(), + ((InlineContentProviderImpl) contentProvider).copy())); + } else { + copiedInlineSuggestions.add(inlineSuggestion); + } + } + return new InlineSuggestionsResponse(copiedInlineSuggestions); + } + + /** * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the * autofill service, potentially filtering the datasets. */ @@ -276,78 +290,20 @@ public final class InlineSuggestionFactory { inlinePresentation.isPinned()); } - private static IInlineContentProvider.Stub createInlineContentProvider( + private static IInlineContentProvider createInlineContentProvider( @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction, @NonNull Runnable onErrorCallback, @NonNull Consumer<IntentSender> intentSenderConsumer, @Nullable RemoteInlineSuggestionRenderService remoteRenderService, @Nullable IBinder hostInputToken, int displayId) { - return new IInlineContentProvider.Stub() { - @Override - public void provideContent(int width, int height, IInlineContentCallback callback) { - UiThread.getHandler().post(() -> { - final IInlineSuggestionUiCallback uiCallback = createInlineSuggestionUiCallback( - callback, onClickAction, onErrorCallback, intentSenderConsumer); - - if (remoteRenderService == null) { - Slog.e(TAG, "RemoteInlineSuggestionRenderService is null"); - return; - } - - remoteRenderService.renderSuggestion(uiCallback, inlinePresentation, - width, height, hostInputToken, displayId); - }); - } - }; - } - - private static IInlineSuggestionUiCallback.Stub createInlineSuggestionUiCallback( - @NonNull IInlineContentCallback callback, @NonNull Runnable onAutofillCallback, - @NonNull Runnable onErrorCallback, - @NonNull Consumer<IntentSender> intentSenderConsumer) { - return new IInlineSuggestionUiCallback.Stub() { - @Override - public void onClick() throws RemoteException { - onAutofillCallback.run(); - callback.onClick(); - } - - @Override - public void onLongClick() throws RemoteException { - callback.onLongClick(); - } - - @Override - public void onContent(SurfaceControlViewHost.SurfacePackage surface, int width, - int height) - throws RemoteException { - callback.onContent(surface, width, height); - surface.release(); - } - - @Override - public void onError() throws RemoteException { - onErrorCallback.run(); - } - - @Override - public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) - throws RemoteException { - final InputMethodManagerInternal inputMethodManagerInternal = - LocalServices.getService(InputMethodManagerInternal.class); - if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken, - displayId)) { - Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME"); - onErrorCallback.run(); - } - } - - @Override - public void onStartIntentSender(IntentSender intentSender) { - intentSenderConsumer.accept(intentSender); - } - }; + RemoteInlineSuggestionViewConnector + remoteInlineSuggestionViewConnector = new RemoteInlineSuggestionViewConnector( + remoteRenderService, inlinePresentation, hostInputToken, displayId, onClickAction, + onErrorCallback, intentSenderConsumer); + InlineContentProviderImpl inlineContentProvider = new InlineContentProviderImpl( + remoteInlineSuggestionViewConnector, null); + return inlineContentProvider; } private InlineSuggestionFactory() { diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java new file mode 100644 index 000000000000..00a5283c9b1f --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill.ui; + +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.IntentSender; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.service.autofill.IInlineSuggestionUi; +import android.service.autofill.IInlineSuggestionUiCallback; +import android.service.autofill.ISurfacePackageResultCallback; +import android.util.Slog; +import android.view.SurfaceControlViewHost; + +import com.android.internal.view.inline.IInlineContentCallback; + +/** + * The instance of this class lives in the system server, orchestrating the communication between + * the remote process owning embedded view (i.e. ExtServices) and the remote process hosting the + * embedded view (i.e. IME). It's also responsible for releasing the embedded view from the owning + * process when it's not longer needed in the hosting process. + * + * <p>An instance of this class may be reused to associate with multiple instances of + * {@link InlineContentProviderImpl}s, each of which wraps a callback from the IME. But at any + * given time, there is only one active IME callback which this class will callback into. + * + * <p>This class is thread safe, because all the outside calls are piped into the same single + * thread handler to be processed. + * + * TODO(b/154683107): implement the reference counting in case there are multiple active + * SurfacePackages at the same time. This will not happen for now since all the InlineSuggestions + * sharing the same UI will be sent to the same IME window, so the previous view will be detached + * before the new view are attached to the window. + */ +final class RemoteInlineSuggestionUi { + + private static final String TAG = RemoteInlineSuggestionUi.class.getSimpleName(); + + // The delay time to release the remote inline suggestion view (in the renderer + // process) after receiving a signal about the surface package being released due to being + // detached from the window in the host app (in the IME process). The release will be + // canceled if the host app reattaches the view to a window within this delay time. + // TODO(b/154683107): try out using the Chroreographer to schedule the release right at the + // next frame. Basically if the view is not re-attached to the window immediately in the next + // frame after it was detached, then it will be released. + private static final long RELEASE_REMOTE_VIEW_HOST_DELAY_MS = 200; + + @NonNull + private final Handler mHandler; + @NonNull + private final RemoteInlineSuggestionViewConnector mRemoteInlineSuggestionViewConnector; + private final int mWidth; + private final int mHeight; + @NonNull + private final InlineSuggestionUiCallbackImpl mInlineSuggestionUiCallback; + + @Nullable + private IInlineContentCallback mInlineContentCallback; // from IME + + /** + * Remote inline suggestion view, backed by an instance of {@link SurfaceControlViewHost} in + * the render service process. We takes care of releasing it when there is no remote + * reference to it (from IME), and we will create a new instance of the view when it's needed + * by IME again. + */ + @Nullable + private IInlineSuggestionUi mInlineSuggestionUi; + private boolean mWaitingForUiCreation = false; + private int mActualWidth; + private int mActualHeight; + + @Nullable + private Runnable mDelayedReleaseViewRunnable; + + RemoteInlineSuggestionUi( + @NonNull RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector, + int width, int height, Handler handler) { + mHandler = handler; + mRemoteInlineSuggestionViewConnector = remoteInlineSuggestionViewConnector; + mWidth = width; + mHeight = height; + mInlineSuggestionUiCallback = new InlineSuggestionUiCallbackImpl(); + } + + /** + * Updates the callback from the IME process. It'll swap out the previous IME callback, and + * all the subsequent callback events (onClick, onLongClick, touch event transfer, etc) will + * be directed to the new callback. + */ + void setInlineContentCallback(@NonNull IInlineContentCallback inlineContentCallback) { + mHandler.post(() -> { + mInlineContentCallback = inlineContentCallback; + }); + } + + /** + * Handles the request from the IME process to get a new surface package. May create a new + * view in the renderer process if the existing view is already released. + */ + void requestSurfacePackage() { + mHandler.post(this::handleRequestSurfacePackage); + } + + /** + * Handles the signal from the IME process that the previously sent surface package has been + * released. + */ + void surfacePackageReleased() { + mHandler.post(this::handleSurfacePackageReleased); + } + + /** + * Returns true if the provided size matches the remote view's size. + */ + boolean match(int width, int height) { + return mWidth == width && mHeight == height; + } + + private void handleSurfacePackageReleased() { + cancelPendingReleaseViewRequest(); + + // Schedule a delayed release view request + mDelayedReleaseViewRunnable = () -> { + if (mInlineSuggestionUi != null) { + try { + mInlineSuggestionUi.releaseSurfaceControlViewHost(); + mInlineSuggestionUi = null; + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling releaseSurfaceControlViewHost"); + } + } + mDelayedReleaseViewRunnable = null; + }; + mHandler.postDelayed(mDelayedReleaseViewRunnable, RELEASE_REMOTE_VIEW_HOST_DELAY_MS); + } + + private void handleRequestSurfacePackage() { + cancelPendingReleaseViewRequest(); + + if (mInlineSuggestionUi == null) { + if (mWaitingForUiCreation) { + // This could happen in the following case: the remote embedded view was released + // when previously detached from window. An event after that to re-attached to + // the window will cause us calling the renderSuggestion again. Now, before the + // render call returns a new surface package, if the view is detached and + // re-attached to the window, causing this method to be called again, we will get + // to this state. This request will be ignored and the surface package will still + // be sent back once the view is rendered. + if (sDebug) Slog.d(TAG, "Inline suggestion ui is not ready"); + } else { + mRemoteInlineSuggestionViewConnector.renderSuggestion(mWidth, mHeight, + mInlineSuggestionUiCallback); + mWaitingForUiCreation = true; + } + } else { + try { + mInlineSuggestionUi.getSurfacePackage(new ISurfacePackageResultCallback.Stub() { + @Override + public void onResult(SurfaceControlViewHost.SurfacePackage result) + throws RemoteException { + if (sDebug) Slog.d(TAG, "Sending new SurfacePackage to IME"); + mInlineContentCallback.onContent(result, mActualWidth, mActualHeight); + } + }); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling getSurfacePackage."); + } + } + } + + private void cancelPendingReleaseViewRequest() { + if (mDelayedReleaseViewRunnable != null) { + mHandler.removeCallbacks(mDelayedReleaseViewRunnable); + mDelayedReleaseViewRunnable = null; + } + } + + /** + * This is called when a new inline suggestion UI is inflated from the ext services. + */ + private void handleInlineSuggestionUiReady(IInlineSuggestionUi content, + SurfaceControlViewHost.SurfacePackage surfacePackage, int width, int height) { + mInlineSuggestionUi = content; + mWaitingForUiCreation = false; + mActualWidth = width; + mActualHeight = height; + if (mInlineContentCallback != null) { + try { + mInlineContentCallback.onContent(surfacePackage, mActualWidth, mActualHeight); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling onContent"); + } + } + if (surfacePackage != null) { + surfacePackage.release(); + } + } + + private void handleOnClick() { + // Autofill the value + mRemoteInlineSuggestionViewConnector.onClick(); + + // Notify the remote process (IME) that hosts the embedded UI that it's clicked + if (mInlineContentCallback != null) { + try { + mInlineContentCallback.onClick(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling onClick"); + } + } + } + + private void handleOnLongClick() { + // Notify the remote process (IME) that hosts the embedded UI that it's long clicked + if (mInlineContentCallback != null) { + try { + mInlineContentCallback.onLongClick(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling onLongClick"); + } + } + } + + private void handleOnError() { + mRemoteInlineSuggestionViewConnector.onError(); + } + + private void handleOnTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) { + mRemoteInlineSuggestionViewConnector.onTransferTouchFocusToImeWindow(sourceInputToken, + displayId); + } + + private void handleOnStartIntentSender(IntentSender intentSender) { + mRemoteInlineSuggestionViewConnector.onStartIntentSender(intentSender); + } + + /** + * Responsible for communicating with the inline suggestion view owning process. + */ + private class InlineSuggestionUiCallbackImpl extends IInlineSuggestionUiCallback.Stub { + + @Override + public void onClick() { + mHandler.post(RemoteInlineSuggestionUi.this::handleOnClick); + } + + @Override + public void onLongClick() { + mHandler.post(RemoteInlineSuggestionUi.this::handleOnLongClick); + } + + @Override + public void onContent(IInlineSuggestionUi content, + SurfaceControlViewHost.SurfacePackage surface, int width, int height) { + mHandler.post(() -> handleInlineSuggestionUiReady(content, surface, width, height)); + } + + @Override + public void onError() { + mHandler.post(RemoteInlineSuggestionUi.this::handleOnError); + } + + @Override + public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) { + mHandler.post(() -> handleOnTransferTouchFocusToImeWindow(sourceInputToken, displayId)); + } + + @Override + public void onStartIntentSender(IntentSender intentSender) { + mHandler.post(() -> handleOnStartIntentSender(intentSender)); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java new file mode 100644 index 000000000000..9d23c171800d --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill.ui; + +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.IntentSender; +import android.os.IBinder; +import android.service.autofill.IInlineSuggestionUiCallback; +import android.service.autofill.InlinePresentation; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.autofill.RemoteInlineSuggestionRenderService; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.function.Consumer; + +/** + * Wraps the parameters needed to create a new inline suggestion view in the remote renderer + * service, and handles the callback from the events on the created remote view. + */ +final class RemoteInlineSuggestionViewConnector { + private static final String TAG = RemoteInlineSuggestionViewConnector.class.getSimpleName(); + + @Nullable + private final RemoteInlineSuggestionRenderService mRemoteRenderService; + @NonNull + private final InlinePresentation mInlinePresentation; + @Nullable + private final IBinder mHostInputToken; + private final int mDisplayId; + + @NonNull + private final Runnable mOnAutofillCallback; + @NonNull + private final Runnable mOnErrorCallback; + @NonNull + private final Consumer<IntentSender> mStartIntentSenderFromClientApp; + + RemoteInlineSuggestionViewConnector( + @Nullable RemoteInlineSuggestionRenderService remoteRenderService, + @NonNull InlinePresentation inlinePresentation, + @Nullable IBinder hostInputToken, + int displayId, + @NonNull Runnable onAutofillCallback, + @NonNull Runnable onErrorCallback, + @NonNull Consumer<IntentSender> startIntentSenderFromClientApp) { + mRemoteRenderService = remoteRenderService; + mInlinePresentation = inlinePresentation; + mHostInputToken = hostInputToken; + mDisplayId = displayId; + + mOnAutofillCallback = onAutofillCallback; + mOnErrorCallback = onErrorCallback; + mStartIntentSenderFromClientApp = startIntentSenderFromClientApp; + } + + /** + * Calls the remote renderer service to create a new inline suggestion view. + * + * @return true if the call is made to the remote renderer service, false otherwise. + */ + public boolean renderSuggestion(int width, int height, + @NonNull IInlineSuggestionUiCallback callback) { + if (mRemoteRenderService != null) { + if (sDebug) Slog.d(TAG, "Request to recreate the UI"); + mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height, + mHostInputToken, mDisplayId); + return true; + } + return false; + } + + /** + * Handles the callback for the event of remote view being clicked. + */ + public void onClick() { + mOnAutofillCallback.run(); + } + + /** + * Handles the callback for the remote error when creating or interacting with the view. + */ + public void onError() { + mOnErrorCallback.run(); + } + + /** + * Handles the callback for transferring the touch event on the remote view to the IME + * process. + */ + public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) { + final InputMethodManagerInternal inputMethodManagerInternal = + LocalServices.getService(InputMethodManagerInternal.class); + if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken, + displayId)) { + Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME"); + mOnErrorCallback.run(); + } + } + + /** + * Handles starting an intent sender from the client app's process. + */ + public void onStartIntentSender(IntentSender intentSender) { + mStartIntentSenderFromClientApp.accept(intentSender); + } +} diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index bfcde97d6c91..829fca66ec0d 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -253,10 +253,7 @@ public class RescueParty { logCriticalInfo(Log.DEBUG, "Finished rescue level " + levelToString(level)); } catch (Throwable t) { - final String msg = ExceptionUtils.getCompleteMessage(t); - EventLogTags.writeRescueFailure(level, msg); - logCriticalInfo(Log.ERROR, - "Failed rescue level " + levelToString(level) + ": " + msg); + logRescueException(level, t); } } @@ -274,11 +271,31 @@ public class RescueParty { resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, failedPackage); break; case LEVEL_FACTORY_RESET: - RecoverySystem.rebootPromptAndWipeUserData(context, TAG); + // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog + // when device shutting down. + Runnable runnable = new Runnable() { + @Override + public void run() { + try { + RecoverySystem.rebootPromptAndWipeUserData(context, TAG); + } catch (Throwable t) { + logRescueException(level, t); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); break; } } + private static void logRescueException(int level, Throwable t) { + final String msg = ExceptionUtils.getCompleteMessage(t); + EventLogTags.writeRescueFailure(level, msg); + logCriticalInfo(Log.ERROR, + "Failed rescue level " + levelToString(level) + ": " + msg); + } + private static int mapRescueLevelToUserImpact(int rescueLevel) { switch(rescueLevel) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index cfb79aa3a210..b1b5ec01df6a 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -169,7 +169,11 @@ public class ServiceWatcher implements ServiceConnection { @Override public String toString() { - return component.toShortString() + "@" + version + "[u" + userId + "]"; + if (component == null) { + return "none"; + } else { + return component.toShortString() + "@" + version + "[u" + userId + "]"; + } } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 14fe0c5e3c22..be539456ae7c 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1579,6 +1579,14 @@ class StorageManagerService extends IStorageManager.Stub writeSettingsLocked(); } } + + if (newState == VolumeInfo.STATE_MOUNTED) { + // Private volumes can be unmounted and re-mounted even after a user has + // been unlocked; on devices that support encryption keys tied to the filesystem, + // this requires setting up the keys again. + prepareUserStorageIfNeeded(vol); + } + // This is a blocking call to Storage Service which needs to process volume state changed // before notifying other listeners. // Intentionally called without the mLock to avoid deadlocking from the Storage Service. @@ -3266,10 +3274,38 @@ class StorageManagerService extends IStorageManager.Stub } } + private void prepareUserStorageIfNeeded(VolumeInfo vol) { + if (vol.type != VolumeInfo.TYPE_PRIVATE) { + return; + } + + final UserManager um = mContext.getSystemService(UserManager.class); + final UserManagerInternal umInternal = + LocalServices.getService(UserManagerInternal.class); + + for (UserInfo user : um.getUsers(false /* includeDying */)) { + final int flags; + if (umInternal.isUserUnlockingOrUnlocked(user.id)) { + flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; + } else if (umInternal.isUserRunning(user.id)) { + flags = StorageManager.FLAG_STORAGE_DE; + } else { + continue; + } + + prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags); + } + } + @Override public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) { enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); + prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags); + } + + private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber, + int flags) { try { mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags); // After preparing user storage, we should check if we should mount data mirror again, diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 70b639846e1e..26cb208a3d47 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -450,6 +450,14 @@ final class UiModeManagerService extends SystemService { return oldNightMode != mNightMode; } + private static long toMilliSeconds(LocalTime t) { + return t.toNanoOfDay() / 1000; + } + + private static LocalTime fromMilliseconds(long t) { + return LocalTime.ofNanoOfDay(t * 1000); + } + private void registerScreenOffEventLocked() { if (mPowerSave) return; mWaitForScreenOff = true; @@ -1385,8 +1393,11 @@ final class UiModeManagerService extends SystemService { pw.println("UiModeManager service (uimode) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" night [yes|no|auto]"); + pw.println(" night [yes|no|auto|custom]"); pw.println(" Set or read night mode."); + pw.println(" time [start|end] <ISO time>"); + pw.println(" Set custom start/end schedule time" + + " (night mode must be set to custom to apply)."); } @Override @@ -1399,6 +1410,8 @@ final class UiModeManagerService extends SystemService { switch (cmd) { case "night": return handleNightMode(); + case "time": + return handleCustomTime(); default: return handleDefaultCommands(cmd); } @@ -1409,6 +1422,34 @@ final class UiModeManagerService extends SystemService { return -1; } + private int handleCustomTime() throws RemoteException { + final String modeStr = getNextArg(); + if (modeStr == null) { + printCustomTime(); + return 0; + } + switch (modeStr) { + case "start": + final String start = getNextArg(); + mInterface.setCustomNightModeStart(toMilliSeconds(LocalTime.parse(start))); + return 0; + case "end": + final String end = getNextArg(); + mInterface.setCustomNightModeEnd(toMilliSeconds(LocalTime.parse(end))); + return 0; + default: + getErrPrintWriter().println("command must be in [start|end]"); + return -1; + } + } + + private void printCustomTime() throws RemoteException { + getOutPrintWriter().println("start " + fromMilliseconds( + mInterface.getCustomNightModeStart()).toString()); + getOutPrintWriter().println("end " + fromMilliseconds( + mInterface.getCustomNightModeEnd()).toString()); + } + private int handleNightMode() throws RemoteException { final PrintWriter err = getErrPrintWriter(); final String modeStr = getNextArg(); @@ -1424,7 +1465,8 @@ final class UiModeManagerService extends SystemService { return 0; } else { err.println("Error: mode must be '" + NIGHT_MODE_STR_YES + "', '" - + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO + "'"); + + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO + + "', or '" + NIGHT_MODE_STR_CUSTOM + "'"); return -1; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8d3515202126..d914bda3eff4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8766,27 +8766,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isUidActiveOrForeground(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "isUidActiveOrForeground"); - } - synchronized (this) { - final boolean isActive = isUidActiveLocked(uid); - if (isActive) { - return true; - } - } - final boolean isForeground = mAtmInternal.isUidForeground(uid); - if (isForeground) { - Slog.wtf(TAG, "isUidActiveOrForeground: isUidActive false but " - + " isUidForeground true, uid:" + uid - + " callingPackage:" + callingPackage); - } - return isForeground; - } - - @Override public void setPersistentVrThread(int tid) { mActivityTaskManager.setPersistentVrThread(tid); } @@ -20201,4 +20180,15 @@ public class ActivityManagerService extends IActivityManager.Stub mUsageStatsService.reportLocusUpdate(activity, userId, locusId, appToken); } } + + @Override + public boolean isAppFreezerSupported() { + final long token = Binder.clearCallingIdentity(); + + try { + return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported(); + } finally { + Binder.restoreCallingIdentity(token); + } + } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 86d9028f53dc..f9d204fa008e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -33,6 +33,7 @@ import android.os.Trace; import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; +import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; @@ -407,7 +408,7 @@ public final class CachedAppOptimizer { /** * Determines whether the freezer is correctly supported by this system */ - public boolean isFreezerSupported() { + public static boolean isFreezerSupported() { boolean supported = false; FileReader fr = null; @@ -443,7 +444,13 @@ public final class CachedAppOptimizer { */ @GuardedBy("mPhenotypeFlagLock") private void updateUseFreezer() { - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(), + Settings.Global.CACHED_APPS_FREEZER_ENABLED); + + if ("disabled".equals(configOverride)) { + mUseFreezer = false; + } else if ("enabled".equals(configOverride) + || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { mUseFreezer = isFreezerSupported(); } diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 604b9f1ead6d..9cd903940a83 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -37,7 +37,12 @@ ] }, { - "name": "CtsAppTestCases:ActivityManagerApi29Test" + "name": "CtsAppTestCases", + "options": [ + { + "include-filter": "android.app.cts.ActivityManagerApi29Test" + } + ] }, { "name": "UidAtomTests:testAppOps" diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 7cac376ea7ae..17baead84f9d 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1217,6 +1217,8 @@ public class AudioService extends IAudioService.Stub */ @NonNull public List<AudioProductStrategy> getAudioProductStrategies() { + // verify permissions + enforceModifyAudioRoutingPermission(); return AudioProductStrategy.getAudioProductStrategies(); } @@ -1226,6 +1228,8 @@ public class AudioService extends IAudioService.Stub */ @NonNull public List<AudioVolumeGroup> getAudioVolumeGroups() { + // verify permissions + enforceModifyAudioRoutingPermission(); return AudioVolumeGroup.getAudioVolumeGroups(); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index bafeb77c55e6..9411c5629457 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1067,12 +1067,22 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f; final boolean brightnessIsTemporary = mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment; - if (initialRampSkip || hasBrightnessBuckets - || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { - animateScreenBrightness(brightnessState, SCREEN_ANIMATION_RATE_MINIMUM); - } else { - animateScreenBrightness(brightnessState, - slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); + // We only want to animate the brightness if it is between 0.0f and 1.0f. + // brightnessState can contain the values -1.0f and NaN, which we do not want to + // animate to. To avoid this, we check the value first. + // If the brightnessState is off (-1.0f) we still want to animate to the minimum + // brightness (0.0f) to accommodate for LED displays, which can appear bright to the + // user even when the display is all black. + float animateValue = brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT + ? PowerManager.BRIGHTNESS_MIN : brightnessState; + if (isValidBrightnessValue(animateValue)) { + if (initialRampSkip || hasBrightnessBuckets + || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { + animateScreenBrightness(animateValue, SCREEN_ANIMATION_RATE_MINIMUM); + } else { + animateScreenBrightness(animateValue, + slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); + } } if (!brightnessIsTemporary) { diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 24e1b4edc8a6..4b6430d5197c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -146,7 +146,7 @@ final class DisplayPowerState { /** * Sets the display brightness. * - * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest). + * @param brightness The brightness, ranges from 0.0f (minimum / off) to 1.0f (brightest). */ public void setScreenBrightness(float brightness) { if (mScreenBrightness != brightness) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 2cae1d64ca34..905a10bd641b 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -571,7 +571,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { // APK signatures is already verified elsewhere in PackageManager. We do not need to // verify it again since it could cause a timeout for large APKs. pkg.setSigningDetails( - ParsingPackageUtils.collectCertificates(pkg, /* skipVerify= */ true)); + ParsingPackageUtils.getSigningDetails(pkg, /* skipVerify= */ true)); return PackageInfoUtils.generate( pkg, null, diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 4f8708a7599a..ccbe96f30e04 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -69,6 +69,7 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; import android.os.ICancellationSignal; +import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; @@ -2600,6 +2601,14 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, + ParcelFileDescriptor err, String[] args) { + return new LocationShellCommand(this).exec( + this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { return; @@ -2658,8 +2667,7 @@ public class LocationManagerService extends ILocationManager.Stub { mRequestStatistics.statistics); for (Map.Entry<PackageProviderKey, PackageStatistics> entry : sorted.entrySet()) { - PackageProviderKey key = entry.getKey(); - ipw.println(key.mPackageName + ": " + key.mProviderName + ": " + entry.getValue()); + ipw.println(entry.getKey() + ": " + entry.getValue()); } ipw.decreaseIndent(); diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java index dcdf48ba08d2..e629b428d864 100644 --- a/services/core/java/com/android/server/location/LocationRequestStatistics.java +++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.SystemClock; import android.util.Log; @@ -25,6 +26,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.Objects; @@ -121,14 +123,30 @@ public class LocationRequestStatistics { this.mProviderName = providerName; } + @NonNull + @Override + public String toString() { + return mProviderName + ": " + mPackageName + + (mFeatureId == null ? "" : ": " + mFeatureId); + } + + /** + * Sort by provider, then package, then feature + */ @Override public int compareTo(PackageProviderKey other) { final int providerCompare = mProviderName.compareTo(other.mProviderName); if (providerCompare != 0) { return providerCompare; - } else { - return mProviderName.compareTo(other.mProviderName); } + + final int packageCompare = mPackageName.compareTo(other.mPackageName); + if (packageCompare != 0) { + return packageCompare; + } + + return Objects.compare(mFeatureId, other.mFeatureId, Comparator + .nullsFirst(String::compareTo)); } @Override diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java new file mode 100644 index 000000000000..909873f07993 --- /dev/null +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import android.os.BasicShellCommandHandler; +import android.os.UserHandle; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * Interprets and executes 'adb shell cmd location [args]'. + */ +class LocationShellCommand extends BasicShellCommandHandler { + + private final LocationManagerService mService; + + LocationShellCommand(LocationManagerService service) { + mService = Objects.requireNonNull(service); + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + + switch (cmd) { + case "set-location-enabled": { + int userId = parseUserId(); + boolean enabled = Boolean.parseBoolean(getNextArgRequired()); + mService.setLocationEnabledForUser(enabled, userId); + return 0; + } + case "send-extra-command": { + String provider = getNextArgRequired(); + String command = getNextArgRequired(); + mService.sendExtraCommand(provider, command, null); + return 0; + } + default: + return handleDefaultCommands(cmd); + } + } + + private int parseUserId() { + final String option = getNextOption(); + if (option != null) { + if (option.equals("--user")) { + return UserHandle.parseUserArg(getNextArgRequired()); + } else { + throw new IllegalArgumentException( + "Expected \"--user\" option, but got \"" + option + "\" instead"); + } + } + + return UserHandle.USER_CURRENT_OR_SELF; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Location service commands:"); + pw.println(" help or -h"); + pw.println(" Print this help text."); + pw.println(" set-location-enabled [--user <USER_ID>] true|false"); + pw.println(" Sets the master location switch enabled state."); + pw.println(" send-extra-command <PROVIDER> <COMMAND>"); + pw.println(" Sends the given extra command to the given provider."); + pw.println(); + pw.println(" Common commands that may be supported by the gps provider, depending on"); + pw.println(" hardware and software configurations:"); + pw.println(" delete_aiding_data - requests deletion of any predictive aiding data"); + pw.println(" force_time_injection - requests NTP time injection to chipset"); + pw.println(" force_psds_injection - " + + "requests predictive aiding data injection to chipset"); + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index a5de90c93aab..a435f1e16b80 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -315,9 +315,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean duplicateSessionAlreadyExists = false; synchronized (mLock) { @@ -348,9 +346,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean found = false; synchronized (mLock) { @@ -380,9 +376,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean found = false; synchronized (mLock) { @@ -403,6 +397,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider mCallback.onSessionReleased(this, sessionInfo); } + private RoutingSessionInfo updateSessionInfo(RoutingSessionInfo sessionInfo) { + return new RoutingSessionInfo.Builder(sessionInfo) + .setOwnerPackageName(mComponentName.getPackageName()) + .setProviderId(getUniqueId()) + .build(); + } + private void onRequestFailed(Connection connection, long requestId, int reason) { if (mActiveConnection != connection) { return; diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ec941c8aea59..3283fd9b2c51 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -799,7 +799,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writePolicyAL(); } - enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true); setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service"); updateRulesForGlobalChangeAL(false); updateNotificationsNL(); @@ -871,6 +870,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { new NetworkRequest.Builder().build(), mNetworkCallback); mAppStandby.addListener(new NetPolicyAppIdleStateChangeListener()); + synchronized (mUidRulesFirstLock) { + updateRulesForAppIdleParoleUL(); + } // Listen for subscriber changes mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener( @@ -3893,6 +3895,39 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** + * Toggle the firewall standby chain and inform listeners if the uid rules have effectively + * changed. + */ + @GuardedBy("mUidRulesFirstLock") + private void updateRulesForAppIdleParoleUL() { + final boolean paroled = mAppStandby.isInParole(); + final boolean enableChain = !paroled; + enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, enableChain); + + int ruleCount = mUidFirewallStandbyRules.size(); + for (int i = 0; i < ruleCount; i++) { + final int uid = mUidFirewallStandbyRules.keyAt(i); + int oldRules = mUidRules.get(uid); + if (enableChain) { + // Chain wasn't enabled before and the other power-related + // chains are whitelists, so we can clear the + // MASK_ALL_NETWORKS part of the rules and re-inform listeners if + // the effective rules result in blocking network access. + oldRules &= MASK_METERED_NETWORKS; + } else { + // Skip if it had no restrictions to begin with + if ((oldRules & MASK_ALL_NETWORKS) == 0) continue; + } + final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules, paroled); + if (newUidRules == RULE_NONE) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, newUidRules); + } + } + } + + /** * Update rules that might be changed by {@link #mRestrictBackground}, * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value. */ @@ -4347,7 +4382,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private void updateRulesForPowerRestrictionsUL(int uid) { final int oldUidRules = mUidRules.get(uid, RULE_NONE); - final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules); + final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false); if (newUidRules == RULE_NONE) { mUidRules.delete(uid); @@ -4361,28 +4396,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * * @param uid the uid of the app to update rules for * @param oldUidRules the current rules for the uid, in order to determine if there's a change + * @param paroled whether to ignore idle state of apps and only look at other restrictions * * @return the new computed rules for the uid */ - private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules) { + private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) { if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, - "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules); + "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/" + + (paroled ? "P" : "-")); } try { - return updateRulesForPowerRestrictionsULInner(uid, oldUidRules); + return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled); } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } } - private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules) { + private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) { if (!isUidValidForBlacklistRules(uid)) { if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid); return RULE_NONE; } - final boolean isIdle = isUidIdle(uid); + final boolean isIdle = !paroled && isUidIdle(uid); final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode; final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid); @@ -4452,6 +4489,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } catch (NameNotFoundException nnfe) { } } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + synchronized (mUidRulesFirstLock) { + mLogger.paroleStateChanged(isParoleOn); + updateRulesForAppIdleParoleUL(); + } + } } private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 8e3de1598275..a9fa2b1bd491 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1382,13 +1382,21 @@ public final class NotificationRecord { */ public boolean isConversation() { Notification notification = getNotification(); - if (mChannel.isDemoted() - || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { + if (!Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { + // very common; don't bother logging + return false; + } + if (mChannel.isDemoted()) { return false; } if (mIsNotConversationOverride) { return false; } + if (mTargetSdkVersion >= Build.VERSION_CODES.R + && Notification.MessagingStyle.class.equals(notification.getNotificationStyle()) + && mShortcutInfo == null) { + return false; + } return true; } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index acce6992ea79..92c145275ac8 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -509,7 +509,7 @@ public abstract class ApexManager { ParsedPackage pp = parseResult.parsedPackage; try { pp.setSigningDetails( - ParsingPackageUtils.collectCertificates(pp, false)); + ParsingPackageUtils.getSigningDetails(pp, false)); } catch (PackageParserException e) { throw new IllegalStateException( "Unable to collect certificates for " + ai.modulePath, e); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 118fdcbcb736..70d1adecc6f3 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -725,7 +725,7 @@ public class AppsFilter { } final PackageSetting callingPkgSetting; final ArraySet<PackageSetting> callingSharedPkgSettings; - Trace.beginSection("callingSetting instanceof"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { callingPkgSetting = (PackageSetting) callingSetting; callingSharedPkgSettings = null; @@ -733,7 +733,7 @@ public class AppsFilter { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; } - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (callingPkgSetting != null) { if (callingPkgSetting.pkg != null @@ -769,7 +769,7 @@ public class AppsFilter { return false; } final String targetName = targetPkg.getPackageName(); - Trace.beginSection("getAppId"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "getAppId"); final int callingAppId; if (callingPkgSetting != null) { callingAppId = callingPkgSetting.appId; @@ -777,7 +777,7 @@ public class AppsFilter { callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same } final int targetAppId = targetPkgSetting.appId; - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (callingAppId == targetAppId) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "same app id"); @@ -786,7 +786,7 @@ public class AppsFilter { } try { - Trace.beginSection("hasPermission"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "hasPermission"); if (callingSetting.getPermissionsState().hasPermission( Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { if (DEBUG_LOGGING) { @@ -795,10 +795,10 @@ public class AppsFilter { return false; } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { - Trace.beginSection("mForceQueryable"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mForceQueryable"); if (mForceQueryable.contains(targetAppId)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "force queryable"); @@ -806,10 +806,10 @@ public class AppsFilter { return false; } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { - Trace.beginSection("mQueriesViaPackage"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaPackage"); if (mQueriesViaPackage.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "queries package"); @@ -817,10 +817,10 @@ public class AppsFilter { return false; } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { - Trace.beginSection("mQueriesViaComponent"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent"); if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "queries component"); @@ -828,11 +828,11 @@ public class AppsFilter { return false; } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { - Trace.beginSection("mImplicitlyQueryable"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable"); final int targetUid = UserHandle.getUid(userId, targetAppId); if (mImplicitlyQueryable.contains(callingUid, targetUid)) { if (DEBUG_LOGGING) { @@ -841,11 +841,11 @@ public class AppsFilter { return false; } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { - Trace.beginSection("mOverlayReferenceMapper"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mOverlayReferenceMapper"); if (callingSharedPkgSettings != null) { int size = callingSharedPkgSettings.size(); for (int index = 0; index < size; index++) { @@ -868,7 +868,7 @@ public class AppsFilter { } } } finally { - Trace.endSection(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 3367cd556b2b..5b5f334803e5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -676,7 +676,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, - stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID, + stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, ""); synchronized (mSessions) { @@ -830,7 +830,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); - return session != null + return (session != null && !(session.isStaged() && session.isDestroyed())) ? session.generateInfoForCaller(true /*withIcon*/, Binder.getCallingUid()) : null; } @@ -851,7 +851,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - if (session.userId == userId && !session.hasParentSessionId()) { + if (session.userId == userId && !session.hasParentSessionId() + && !(session.isStaged() && session.isDestroyed())) { result.add(session.generateInfoForCaller(false, callingUid)); } } @@ -1269,7 +1270,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void onStagedSessionChanged(PackageInstallerSession session) { session.markUpdated(); writeSessionsAsync(); - if (mOkToSendBroadcasts) { + if (mOkToSendBroadcasts && !session.isDestroyed()) { // we don't scrub the data here as this is sent only to the installer several // privileged system packages mPm.sendSessionUpdatedBroadcast( diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 3df044fab30d..e0d057a8d7f3 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -86,6 +86,8 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Binder; @@ -188,6 +190,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_COMMITTED = "committed"; + private static final String ATTR_DESTROYED = "destroyed"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MULTI_PACKAGE = "multiPackage"; private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; @@ -531,7 +534,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, File stageDir, String stageCid, InstallationFile[] files, boolean prepared, - boolean committed, boolean sealed, + boolean committed, boolean destroyed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, String stagedSessionErrorMessage) { @@ -577,6 +580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPrepared = prepared; mCommitted = committed; + mDestroyed = destroyed; mStagedSessionReady = isReady; mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; @@ -711,6 +715,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** {@hide} */ + boolean isDestroyed() { + synchronized (mLock) { + return mDestroyed; + } + } + /** Returns true if a staged session has reached a final state and can be forgotten about */ public boolean isStagedAndInTerminalState() { synchronized (mLock) { @@ -1066,6 +1077,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** + * Check if the caller is the owner of this session. Otherwise throw a + * {@link SecurityException}. + */ + @GuardedBy("mLock") + private void assertCallerIsOwnerOrRootOrSystemLocked() { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid + && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Session does not belong to uid " + callingUid); + } + } + + /** * If anybody is reading or writing data of the session, throw an {@link SecurityException}. */ @GuardedBy("mLock") @@ -2016,15 +2040,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Verify that all staged packages are internally consistent final ArraySet<String> stagedSplits = new ArraySet<>(); + ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); for (File addedFile : addedFiles) { - final ApkLite apk; - try { - apk = ApkLiteParseUtils.parseApkLite( - addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES); - } catch (PackageParserException e) { - throw PackageManagerException.from(e); + ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(), + addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES); + if (result.isError()) { + throw new PackageManagerException(result.getErrorCode(), + result.getErrorMessage(), result.getException()); } + final ApkLite apk = result.getResult(); if (!stagedSplits.add(apk.splitName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Split " + apk.splitName + " was defined multiple times"); @@ -2113,16 +2138,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } else { - final PackageLite existing; - final ApkLite existingBase; ApplicationInfo appInfo = pkgInfo.applicationInfo; - try { - existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0); - existingBase = ApkLiteParseUtils.parseApkLite(new File(appInfo.getBaseCodePath()), - PackageParser.PARSE_COLLECT_CERTIFICATES); - } catch (PackageParserException e) { - throw PackageManagerException.from(e); - } + ParseResult<PackageLite> pkgLiteResult = ApkLiteParseUtils.parsePackageLite( + input.reset(), new File(appInfo.getCodePath()), 0); + if (pkgLiteResult.isError()) { + throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + pkgLiteResult.getErrorMessage(), pkgLiteResult.getException()); + } + final PackageLite existing = pkgLiteResult.getResult(); + ParseResult<ApkLite> apkLiteResult = ApkLiteParseUtils.parseApkLite(input.reset(), + new File(appInfo.getBaseCodePath()), + PackageParser.PARSE_COLLECT_CERTIFICATES); + if (apkLiteResult.isError()) { + throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + apkLiteResult.getErrorMessage(), apkLiteResult.getException()); + } + final ApkLite existingBase = apkLiteResult.getResult(); assertApkConsistentLocked("Existing base", existingBase); @@ -2543,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + mParentSessionId + " and may not be abandoned directly."); } synchronized (mLock) { - assertCallerIsOwnerOrRootLocked(); + if (params.isStaged && mDestroyed) { + // If a user abandons staged session in an unsafe state, then system will try to + // abandon the destroyed staged session when it is safe on behalf of the user. + assertCallerIsOwnerOrRootOrSystemLocked(); + } else { + assertCallerIsOwnerOrRootLocked(); + } if (isStagedAndInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be @@ -2553,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } if (mCommitted && params.isStaged) { - synchronized (mLock) { - mDestroyed = true; + mDestroyed = true; + if (!mStagingManager.abortCommittedSessionLocked(this)) { + // Do not clean up the staged session from system. It is not safe yet. + mCallback.onStagedSessionChanged(this); + return; } - mStagingManager.abortCommittedSession(this); - cleanStageDir(); } @@ -2917,6 +2955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionReady() { synchronized (mLock) { + if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = true; mStagedSessionApplied = false; mStagedSessionFailed = false; @@ -2930,6 +2969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) { synchronized (mLock) { + if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; @@ -2944,6 +2984,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionApplied() { synchronized (mLock) { + if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = true; mStagedSessionFailed = false; @@ -3188,7 +3229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException { synchronized (mLock) { - if (mDestroyed) { + if (mDestroyed && !params.isStaged) { return; } @@ -3214,6 +3255,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted()); + writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed()); writeBooleanAttribute(out, ATTR_SEALED, isSealed()); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); @@ -3343,6 +3385,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean committed = readBooleanAttribute(in, ATTR_COMMITTED); + final boolean destroyed = readBooleanAttribute(in, ATTR_DESTROYED); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID, SessionInfo.INVALID_ID); @@ -3464,7 +3507,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileArray, - prepared, committed, sealed, childSessionIdsArray, parentSessionId, + prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f6758d7b4dff..f31dbbf077bb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -223,6 +223,7 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; +import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedInstrumentation; @@ -232,6 +233,8 @@ import android.content.pm.parsing.component.ParsedPermission; import android.content.pm.parsing.component.ParsedProcess; import android.content.pm.parsing.component.ParsedProvider; import android.content.pm.parsing.component.ParsedService; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.Resources; import android.content.rollback.IRollbackManager; import android.database.ContentObserver; @@ -9057,7 +9060,7 @@ public class PackageManagerService extends IPackageManager.Stub try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); parsedPackage.setSigningDetails( - ParsingPackageUtils.collectCertificates(parsedPackage, skipVerify)); + ParsingPackageUtils.getSigningDetails(parsedPackage, skipVerify)); } catch (PackageParserException e) { throw PackageManagerException.from(e); } finally { @@ -15241,12 +15244,21 @@ public class PackageManagerService extends IPackageManager.Stub && mIntegrityVerificationCompleted && mEnableRollbackCompleted) { if ((installFlags & PackageManager.INSTALL_DRY_RUN) != 0) { String packageName = ""; - try { - PackageLite packageInfo = - new PackageParser().parsePackageLite(origin.file, 0); - packageName = packageInfo.packageName; - } catch (PackageParserException e) { - Slog.e(TAG, "Can't parse package at " + origin.file.getAbsolutePath(), e); + ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + new ParseTypeImpl( + (changeId, packageName1, targetSdkVersion) -> { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName1; + appInfo.targetSdkVersion = targetSdkVersion; + return mPackageParserCallback.isChangeEnabled(changeId, + appInfo); + }).reset(), + origin.file, 0); + if (result.isError()) { + Slog.e(TAG, "Can't parse package at " + origin.file.getAbsolutePath(), + result.getException()); + } else { + packageName = result.getResult().packageName; } try { observer.onPackageInstalled(packageName, mRet, "Dry run", new Bundle()); @@ -17055,7 +17067,7 @@ public class PackageManagerService extends IPackageManager.Stub parsedPackage.setSigningDetails(args.signingDetails); } else { parsedPackage.setSigningDetails( - ParsingPackageUtils.collectCertificates(parsedPackage, false /* skipVerify */)); + ParsingPackageUtils.getSigningDetails(parsedPackage, false /* skipVerify */)); } } catch (PackageParserException e) { throw new PrepareFailure("Failed collect during installPackageLI", e); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 88f442c7b6a0..bc94528c07f0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -51,7 +51,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -63,6 +62,8 @@ import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.ISnapshotRuntimeProfileCallback; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.rollback.IRollbackManager; @@ -505,6 +506,7 @@ class PackageManagerShellCommand extends ShellCommand { long sessionSize = 0; + ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); for (String inPath : inPaths) { final ParcelFileDescriptor fd = openFileForSystem(inPath, "r"); if (fd == null) { @@ -512,12 +514,19 @@ class PackageManagerShellCommand extends ShellCommand { throw new IllegalArgumentException("Error: Can't open file: " + inPath); } try { - ApkLite baseApk = ApkLiteParseUtils.parseApkLite(fd.getFileDescriptor(), inPath, 0); - PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, - null, null); + ParseResult<ApkLite> apkLiteResult = ApkLiteParseUtils.parseApkLite( + input.reset(), fd.getFileDescriptor(), inPath, 0); + if (apkLiteResult.isError()) { + throw new IllegalArgumentException( + "Error: Failed to parse APK file: " + inPath + ": " + + apkLiteResult.getErrorMessage(), + apkLiteResult.getException()); + } + PackageLite pkgLite = new PackageLite(null, apkLiteResult.getResult(), null, null, + null, null, null, null); sessionSize += PackageHelper.calculateInstalledSize(pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()); - } catch (PackageParserException | IOException e) { + } catch (IOException e) { getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath); throw new IllegalArgumentException( "Error: Failed to parse APK file: " + inPath, e); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9a297d601a6b..a83fa32ec9a9 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -61,6 +61,7 @@ import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.apk.ApkSignatureVerifier; @@ -137,6 +138,9 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); + if (stagedSession.isDestroyed()) { + continue; + } result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid)); } } @@ -202,7 +206,7 @@ public class StagingManager { final IntArray childSessionIds = new IntArray(); if (session.isMultiPackage()) { for (int id : session.getChildSessionIds()) { - if (isApexSession(mStagedSessions.get(id))) { + if (isApexSession(getStagedSession(id))) { childSessionIds.add(id); } } @@ -797,6 +801,8 @@ public class StagingManager { + session.sessionId + " [" + errorMessage + "]"); session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage); + mPreRebootVerificationHandler.onPreRebootVerificationComplete( + session.sessionId); return; } mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( @@ -880,7 +886,8 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); - if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { + if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState() + || stagedSession.isDestroyed()) { continue; } if (stagedSession.isMultiPackage()) { @@ -943,27 +950,68 @@ public class StagingManager { } } - void abortCommittedSession(@NonNull PackageInstallerSession session) { + /** + * <p>Abort committed staged session + * + * <p>This method must be called while holding {@link PackageInstallerSession.mLock}. + * + * <p>The method returns {@code false} to indicate it is not safe to clean up the session from + * system yet. When it is safe, the method returns {@code true}. + * + * <p> When it is safe to clean up, {@link StagingManager} will call + * {@link PackageInstallerSession#abandon()} on the session again. + * + * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}. + */ + boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) { + int sessionId = session.sessionId; if (session.isStagedSessionApplied()) { - Slog.w(TAG, "Cannot abort applied session : " + session.sessionId); - return; + Slog.w(TAG, "Cannot abort applied session : " + sessionId); + return false; + } + if (!session.isDestroyed()) { + throw new IllegalStateException("Committed session must be destroyed before aborting it" + + " from StagingManager"); + } + if (getStagedSession(sessionId) == null) { + Slog.w(TAG, "Session " + sessionId + " has been abandoned already"); + return false; } - abortSession(session); - boolean hasApex = sessionContainsApex(session); - if (hasApex) { - ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); - if (apexSession == null || isApexSessionFinalized(apexSession)) { - Slog.w(TAG, - "Cannot abort session " + session.sessionId - + " because it is not active or APEXD is not reachable"); - return; - } - try { - mApexManager.abortStagedSession(session.sessionId); - } catch (Exception ignore) { + // If pre-reboot verification is running, then return false. StagingManager will call + // abandon again when pre-reboot verification ends. + if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) { + Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot " + + "verification completed."); + return false; + } + + // A session could be marked ready once its pre-reboot verification ends + if (session.isStagedSessionReady()) { + if (sessionContainsApex(session)) { + try { + ApexSessionInfo apexSession = + mApexManager.getStagedSessionInfo(session.sessionId); + if (apexSession == null || isApexSessionFinalized(apexSession)) { + Slog.w(TAG, + "Cannot abort session " + session.sessionId + + " because it is not active."); + } else { + mApexManager.abortStagedSession(session.sessionId); + } + } catch (Exception e) { + // Failed to contact apexd service. The apex might still be staged. We can still + // safely cleanup the staged session since pre-reboot verification is complete. + // Also, cleaning up the stageDir prevents the apex from being activated. + Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId); + } } } + + // Session was successfully aborted from apexd (if required) and pre-reboot verification + // is also complete. It is now safe to clean up the session from system. + abortSession(session); + return true; } private boolean isApexSessionFinalized(ApexSessionInfo session) { @@ -1042,6 +1090,11 @@ public class StagingManager { // Final states, nothing to do. return; } + if (session.isDestroyed()) { + // Device rebooted before abandoned session was cleaned up. + session.abandon(); + return; + } if (!session.isStagedSessionReady()) { // The framework got restarted before the pre-reboot verification could complete, // restart the verification. @@ -1124,10 +1177,20 @@ public class StagingManager { } } + private PackageInstallerSession getStagedSession(int sessionId) { + PackageInstallerSession session; + synchronized (mStagedSessions) { + session = mStagedSessions.get(sessionId); + } + return session; + } + private final class PreRebootVerificationHandler extends Handler { // Hold session ids before handler gets ready to do the verification. private IntArray mPendingSessionIds; private boolean mIsReady; + @GuardedBy("mVerificationRunning") + private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray(); PreRebootVerificationHandler(Looper looper) { super(looper); @@ -1155,13 +1218,15 @@ public class StagingManager { @Override public void handleMessage(Message msg) { final int sessionId = msg.arg1; - final PackageInstallerSession session; - synchronized (mStagedSessions) { - session = mStagedSessions.get(sessionId); - } - // Maybe session was aborted before pre-reboot verification was complete + final PackageInstallerSession session = getStagedSession(sessionId); if (session == null) { - Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId); + Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: " + + sessionId); + return; + } + if (session.isDestroyed()) { + // No point in running verification on a destroyed session + onPreRebootVerificationComplete(sessionId); return; } switch (msg.what) { @@ -1200,9 +1265,40 @@ public class StagingManager { mPendingSessionIds.add(sessionId); return; } + + PackageInstallerSession session = getStagedSession(sessionId); + synchronized (mVerificationRunning) { + // Do not start verification on a session that has been abandoned + if (session == null || session.isDestroyed()) { + return; + } + Slog.d(TAG, "Starting preRebootVerification for session " + sessionId); + mVerificationRunning.put(sessionId, true); + } obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget(); } + // Things to do when pre-reboot verification completes for a particular sessionId + private void onPreRebootVerificationComplete(int sessionId) { + // Remove it from mVerificationRunning so that verification is considered complete + synchronized (mVerificationRunning) { + Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId); + mVerificationRunning.delete(sessionId); + } + // Check if the session was destroyed while pre-reboot verification was running. If so, + // abandon it again. + PackageInstallerSession session = getStagedSession(sessionId); + if (session != null && session.isDestroyed()) { + session.abandon(); + } + } + + private boolean isVerificationRunning(int sessionId) { + synchronized (mVerificationRunning) { + return mVerificationRunning.get(sessionId); + } + } + private void notifyPreRebootVerification_Start_Complete(int sessionId) { obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget(); } @@ -1221,8 +1317,6 @@ public class StagingManager { * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification */ private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) { - Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId); - if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { // If rollback is enabled for this session, we call through to the RollbackManager // with the list of sessions it must enable rollback for. Note that @@ -1269,6 +1363,7 @@ public class StagingManager { } } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); + onPreRebootVerificationComplete(session.sessionId); return; } @@ -1301,6 +1396,7 @@ public class StagingManager { // TODO(b/118865310): abort the session on apexd. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); + onPreRebootVerificationComplete(session.sessionId); } } @@ -1323,9 +1419,18 @@ public class StagingManager { Slog.e(TAG, "Failed to get hold of StorageManager", e); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, "Failed to get hold of StorageManager"); + onPreRebootVerificationComplete(session.sessionId); return; } + // Stop pre-reboot verification before marking session ready. From this point on, if we + // abandon the session then it will be cleaned up immediately. If session is abandoned + // after this point, then even if for some reason system tries to install the session + // or activate its apex, there won't be any files to work with as they will be cleaned + // up by the system as part of abandonment. If session is abandoned before this point, + // then the session is already destroyed and cannot be marked ready anymore. + onPreRebootVerificationComplete(session.sessionId); + // Proactively mark session as ready before calling apexd. Although this call order // looks counter-intuitive, this is the easiest way to ensure that session won't end up // in the inconsistent state: @@ -1337,15 +1442,16 @@ public class StagingManager { // only apex part of the train will be applied, leaving device in an inconsistent state. Slog.d(TAG, "Marking session " + session.sessionId + " as ready"); session.setStagedSessionReady(); - final boolean hasApex = sessionContainsApex(session); - if (!hasApex) { - // Session doesn't contain apex, nothing to do. - return; - } - try { - mApexManager.markStagedSessionReady(session.sessionId); - } catch (PackageManagerException e) { - session.setStagedSessionFailed(e.error, e.getMessage()); + if (session.isStagedSessionReady()) { + final boolean hasApex = sessionContainsApex(session); + if (hasApex) { + try { + mApexManager.markStagedSessionReady(session.sessionId); + } catch (PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); + return; + } + } } } } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index eb79b6ec652a..d3cd1a90b0b6 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -37,6 +37,9 @@ }, { "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" + }, + { + "include-filter": "android.content.pm.cts.PackageManagerTest" } ] }, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index e6af86e52035..16d96d9a5574 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -4516,8 +4516,8 @@ public class UserManagerService extends IUserManager.Stub { switch(cmd) { case "list": return runList(pw, shell); - case "list-missing-system-packages": - return runListMissingSystemPackages(pw, shell); + case "report-system-user-package-whitelist-problems": + return runReportPackageWhitelistProblems(pw, shell); default: return shell.handleDefaultCommands(cmd); } @@ -4584,17 +4584,22 @@ public class UserManagerService extends IUserManager.Stub { } } - private int runListMissingSystemPackages(PrintWriter pw, Shell shell) { + private int runReportPackageWhitelistProblems(PrintWriter pw, Shell shell) { boolean verbose = false; - boolean force = false; + boolean criticalOnly = false; + int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE; String opt; while ((opt = shell.getNextOption()) != null) { switch (opt) { case "-v": + case "--verbose": verbose = true; break; - case "--force": - force = true; + case "--critical-only": + criticalOnly = true; + break; + case "--mode": + mode = Integer.parseInt(shell.getNextArgRequired()); break; default: pw.println("Invalid option: " + opt); @@ -4602,8 +4607,12 @@ public class UserManagerService extends IUserManager.Stub { } } + Slog.d(LOG_TAG, "runReportPackageWhitelistProblems(): verbose=" + verbose + + ", criticalOnly=" + criticalOnly + + ", mode=" + UserSystemPackageInstaller.modeToString(mode)); + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { - mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose); + mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose, criticalOnly); } return 0; } @@ -5176,13 +5185,18 @@ public class UserManagerService extends IUserManager.Stub { final PrintWriter pw = getOutPrintWriter(); pw.println("User manager (user) commands:"); pw.println(" help"); - pw.println(" Print this help text."); + pw.println(" Prints this help text."); pw.println(""); pw.println(" list [-v] [-all]"); pw.println(" Prints all users on the system."); - pw.println(" list-missing-system-packages [-v] [--force]"); - pw.println(" Prints all system packages that were not explicitly configured to be " - + "installed."); + pw.println(" report-system-user-package-whitelist-problems [-v | --verbose] " + + "[--critical-only] [--mode MODE]"); + pw.println(" Reports all issues on user-type package whitelist XML files. Options:"); + pw.println(" -v | --verbose : shows extra info, like number of issues"); + pw.println(" --critical-only: show only critical issues, excluding warnings"); + pw.println(" --mode MODE: shows what errors would be if device used mode MODE (where" + + " MODE is the whitelist mode integer as defined by " + + "config_userTypePackageWhitelistMode)"); } } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 0b6024a84f78..1fec8aa0a3ff 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -208,7 +208,6 @@ public class UserRestrictionsUtils { Sets.newArraySet( UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_CAMERA, - UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_BLUETOOTH_SHARING, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index cd1087f5fcd7..9ec03e5e8f5c 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -27,7 +27,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Pair; +import android.util.DebugUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -41,6 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -111,14 +112,20 @@ class UserSystemPackageInstaller { * frameworks/base/core/res/res/values/config.xml */ static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode"; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08; - static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10; + + // NOTE: flags below are public so they can used by DebugUtils.flagsToString. And this class + // itself is package-protected, so it doesn't matter... + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00; + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01; + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02; + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04; + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08; + public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10; static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1; + // Used by Shell command only + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_NONE = -1000; + @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = { USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE, USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE, @@ -266,58 +273,56 @@ class UserSystemPackageInstaller { if (!isLogMode(mode) && !isEnforceMode(mode)) { return; } - final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); - final int size = warnings.size(); - if (size == 0) { - Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings"); - return; + Slog.v(TAG, "Checking that all system packages are whitelisted."); + + // Check whether all whitelisted packages are indeed on the system. + final List<String> warnings = getPackagesWhitelistWarnings(); + final int numberWarnings = warnings.size(); + if (numberWarnings == 0) { + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + + ") has no warnings"); + } else { + Slog.w(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + + ") has " + numberWarnings + " warnings:"); + for (int i = 0; i < numberWarnings; i++) { + Slog.w(TAG, warnings.get(i)); + } } + // Check whether all system packages are indeed whitelisted. if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { - // Only shows whether all whitelisted packages are indeed on the system. - for (int i = 0; i < size; i++) { - final Pair<Boolean, String> pair = warnings.get(i); - final boolean isSevere = pair.first; - if (!isSevere) { - final String msg = pair.second; - Slog.w(TAG, msg); - } - } return; } - Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings"); + final List<String> errors = getPackagesWhitelistErrors(mode); + final int numberErrors = errors.size(); + + if (numberErrors == 0) { + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + + ") has no errors"); + return; + } + Slog.e(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + ") has " + + numberErrors + " errors:"); + boolean doWtf = !isImplicitWhitelistMode(mode); - for (int i = 0; i < size; i++) { - final Pair<Boolean, String> pair = warnings.get(i); - final boolean isSevere = pair.first; - final String msg = pair.second; - if (isSevere) { - if (doWtf) { - Slog.wtf(TAG, msg); - } else { - Slog.e(TAG, msg); - } + for (int i = 0; i < numberWarnings; i++) { + final String msg = errors.get(i); + if (doWtf) { + Slog.wtf(TAG, msg); } else { - Slog.w(TAG, msg); + Slog.e(TAG, msg); } } } - // TODO: method below was created to refactor the one-time logging logic so it can be used on - // dump / cmd as well. It could to be further refactored (for example, creating a new - // structure for the warnings so it doesn't need a Pair). /** - * Gets warnings for system user whitelisting. - * - * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF, - * {@code false} for WARN) and {@code Pair.second} the message. + * Gets packages that are listed in the whitelist XML but are not present on the system image. */ @NonNull - private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings( - @PackageWhitelistMode int mode) { + private List<String> getPackagesWhitelistWarnings() { final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages(); - final List<Pair<Boolean, String>> warnings = new ArrayList<>(); + final List<String> warnings = new ArrayList<>(); final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); // Check whether all whitelisted packages are indeed on the system. @@ -326,25 +331,39 @@ class UserSystemPackageInstaller { for (String pkgName : allWhitelistedPackages) { final AndroidPackage pkg = pmInt.getPackage(pkgName); if (pkg == null) { - warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName))); + warnings.add(String.format(notPresentFmt, pkgName)); } else if (!pkg.isSystem()) { - warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName))); + warnings.add(String.format(notSystemFmt, pkgName)); } } + return warnings; + } + + /** + * Gets packages that are not listed in the whitelist XMLs when they should be. + */ + @NonNull + private List<String> getPackagesWhitelistErrors(@PackageWhitelistMode int mode) { + if ((!isEnforceMode(mode) || isImplicitWhitelistMode(mode)) && !isLogMode(mode)) { + return Collections.emptyList(); + } + + final List<String> errors = new ArrayList<>(); + final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages(); + final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); // Check whether all system packages are indeed whitelisted. final String logMessageFmt = "System package %s is not whitelisted using " + "'install-in-user-type' in SystemConfig for any user types!"; - final boolean isSevere = isEnforceMode(mode); pmInt.forEachPackage(pkg -> { if (!pkg.isSystem()) return; final String pkgName = pkg.getManifestPackageName(); if (!allWhitelistedPackages.contains(pkgName)) { - warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName))); + errors.add(String.format(logMessageFmt, pkgName)); } }); - return warnings; + return errors; } /** Whether to only install system packages in new users for which they are whitelisted. */ @@ -420,10 +439,28 @@ class UserSystemPackageInstaller { if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) { return runtimeMode; } + return getDeviceDefaultWhitelistMode(); + } + + /** Gets the PackageWhitelistMode as defined by {@code config_userTypePackageWhitelistMode}. */ + private @PackageWhitelistMode int getDeviceDefaultWhitelistMode() { return Resources.getSystem() .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode); } + static @NonNull String modeToString(@PackageWhitelistMode int mode) { + // Must handle some types separately because they're not bitwise flags + switch (mode) { + case USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT: + return "DEVICE_DEFAULT"; + case USER_TYPE_PACKAGE_WHITELIST_MODE_NONE: + return "NONE"; + default: + return DebugUtils.flagsToString(UserSystemPackageInstaller.class, + "USER_TYPE_PACKAGE_WHITELIST_MODE_", mode); + } + } + /** * Gets the system packages names that should be installed on the given user. * See {@link #getInstallablePackagesForUserType(String)}. @@ -703,34 +740,44 @@ class UserSystemPackageInstaller { pw.decreaseIndent(); pw.decreaseIndent(); pw.increaseIndent(); - dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true); + dumpPackageWhitelistProblems(pw, mode, /* verbose= */ true, /* criticalOnly= */ false); pw.decreaseIndent(); } - void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) { - final int mode = getWhitelistMode(); - final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode)); - if (!show) return; + void dumpPackageWhitelistProblems(IndentingPrintWriter pw, @PackageWhitelistMode int mode, + boolean verbose, boolean criticalOnly) { + // Handle special cases first + if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_NONE) { + mode = getWhitelistMode(); + } else if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) { + mode = getDeviceDefaultWhitelistMode(); + } + Slog.v(TAG, "dumpPackageWhitelistProblems(): using mode " + modeToString(mode)); + + final List<String> errors = getPackagesWhitelistErrors(mode); + showIssues(pw, verbose, errors, "errors"); - final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); - final int size = warnings.size(); + if (criticalOnly) return; + final List<String> warnings = getPackagesWhitelistWarnings(); + showIssues(pw, verbose, warnings, "warnings"); + } + + private static void showIssues(IndentingPrintWriter pw, boolean verbose, List<String> issues, + String issueType) { + final int size = issues.size(); if (size == 0) { if (verbose) { - pw.println("All system packages are accounted for"); + pw.print("No "); pw.println(issueType); } return; } - if (verbose) { - pw.print(size); pw.println(" warnings for system user:"); + pw.print(size); pw.print(' '); pw.println(issueType); pw.increaseIndent(); } for (int i = 0; i < size; i++) { - final Pair<Boolean, String> pair = warnings.get(i); - final String lvl = pair.first ? "WTF" : "WARN"; - final String msg = pair.second; - pw.print(lvl); pw.print(": "); pw.println(msg); + pw.println(issues.get(i)); } if (verbose) { pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index e860c4857bf4..1145057452c2 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -25,6 +25,7 @@ import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.parsing.ParsingPackage; import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; @@ -35,7 +36,7 @@ import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Slog; -import com.android.server.compat.PlatformCompat; +import com.android.internal.compat.IPlatformCompat; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; @@ -58,14 +59,21 @@ public class PackageParser2 implements AutoCloseable { * * This must be called inside the system process as it relies on {@link ServiceManager}. */ + @NonNull public static PackageParser2 forParsingFileWithDefaults() { - PlatformCompat platformCompat = - (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); return new PackageParser2(null /* separateProcesses */, false /* onlyCoreApps */, null /* displayMetrics */, null /* cacheDir */, new Callback() { @Override public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { - return platformCompat.isChangeEnabled(changeId, appInfo); + try { + return platformCompat.isChangeEnabled(changeId, appInfo); + } catch (Exception e) { + // This shouldn't happen, but assume enforcement if it does + Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e); + return true; + } } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index f17890334b6d..a967f3d7c4f9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -149,6 +149,8 @@ import com.android.server.policy.SoftRestrictedPermissionPolicy; import libcore.util.EmptyArray; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -417,6 +419,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { LocalServices.addService(PermissionManagerInternal.class, localService); } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.getSystemService(PermissionControllerManager.class).dump(fd, pw, args); + } + /** * Creates and returns an initialized, internal service for use by other components. * <p> diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2f84a99774f4..3bc151af3589 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1222,10 +1222,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case LONG_PRESS_POWER_NOTHING: break; case LONG_PRESS_POWER_GLOBAL_ACTIONS: - mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, - "Power - Long Press - Global Actions"); - showGlobalActionsInternal(); + if (!mPowerKeyHandled) { + mPowerKeyHandled = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + "Power - Long Press - Global Actions"); + showGlobalActionsInternal(); + } break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 6726cc829c81..3336697ef359 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -37,6 +37,9 @@ import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -791,13 +794,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } // Get information about the package to be installed. - PackageParser.PackageLite newPackage; - try { - newPackage = PackageParser.parsePackageLite(new File(session.resolvedBaseCodePath), 0); - } catch (PackageParser.PackageParserException e) { - Slog.e(TAG, "Unable to parse new package", e); + ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + ParseResult<PackageParser.ApkLite> parseResult = ApkLiteParseUtils.parseApkLite( + input.reset(), new File(session.resolvedBaseCodePath), 0); + if (parseResult.isError()) { + Slog.e(TAG, "Unable to parse new package: " + parseResult.getErrorMessage(), + parseResult.getException()); return false; } + PackageParser.ApkLite newPackage = parseResult.getResult(); String packageName = newPackage.packageName; Slog.i(TAG, "Enabling rollback for install of " + packageName diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index b0c702f55821..3b4c4235d8a4 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -30,6 +30,7 @@ import static android.util.MathUtils.abs; import static android.util.MathUtils.constrain; import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; +import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_TRUNCATE_TIMESTAMP; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; @@ -755,6 +756,13 @@ public class StatsPullAtomService extends SystemService { stats.getValues(j, entry); StatsEvent.Builder e = StatsEvent.newBuilder(); e.setAtomId(atomTag); + switch (atomTag) { + case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: + case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: + e.addBooleanAnnotation(ANNOTATION_ID_TRUNCATE_TIMESTAMP, true); + break; + default: + } e.writeInt(entry.uid); e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); if (withFgbg) { diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 8f71943129fa..2314afc787c3 100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -46,6 +46,8 @@ import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputService.PriorityHintUseCaseType; import android.media.tv.TvStreamConfig; +import android.media.tv.tunerresourcemanager.ResourceClientProfile; +import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -179,7 +181,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); return; } - connection.resetLocked(null, null, null, null, null); + connection.resetLocked(null, null, null, null, null, null); mConnections.remove(deviceId); buildHardwareListLocked(); TvInputHardwareInfo info = connection.getHardwareInfoLocked(); @@ -369,25 +371,34 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (callback == null) { throw new NullPointerException(); } + TunerResourceManager trm = (TunerResourceManager) mContext.getSystemService( + Context.TV_TUNER_RESOURCE_MGR_SERVICE); synchronized (mLock) { Connection connection = mConnections.get(deviceId); if (connection == null) { Slog.e(TAG, "Invalid deviceId : " + deviceId); return null; } - // TODO: check with TRM to compare the client's priority with the current holder's - // priority. If lower, do nothing. - if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { - TvInputHardwareImpl hardware = - new TvInputHardwareImpl(connection.getHardwareInfoLocked()); - try { - callback.asBinder().linkToDeath(connection, 0); - } catch (RemoteException e) { - hardware.release(); - return null; - } - connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); + + ResourceClientProfile profile = + new ResourceClientProfile(tvInputSessionId, priorityHint); + ResourceClientProfile holderProfile = connection.getResourceClientProfileLocked(); + if (holderProfile != null && trm != null + && !trm.isHigherPriority(profile, holderProfile)) { + Slog.d(TAG, "Acquiring does not show higher priority than the current holder." + + " Device id:" + deviceId); + return null; } + TvInputHardwareImpl hardware = + new TvInputHardwareImpl(connection.getHardwareInfoLocked()); + try { + callback.asBinder().linkToDeath(connection, 0); + } catch (RemoteException e) { + hardware.release(); + return null; + } + connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId, + profile); return connection.getHardwareLocked(); } } @@ -411,7 +422,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (callback != null) { callback.asBinder().unlinkToDeath(connection, 0); } - connection.resetLocked(null, null, null, null, null); + connection.resetLocked(null, null, null, null, null, null); } } @@ -621,6 +632,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { private Integer mCallingUid = null; private Integer mResolvedUserId = null; private Runnable mOnFirstFrameCaptured; + private ResourceClientProfile mResourceClientProfile = null; public Connection(TvInputHardwareInfo hardwareInfo) { mHardwareInfo = hardwareInfo; @@ -629,7 +641,8 @@ class TvInputHardwareManager implements TvInputHal.Callback { // *Locked methods assume TvInputHardwareManager.mLock is held. public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, - TvInputInfo info, Integer callingUid, Integer resolvedUserId) { + TvInputInfo info, Integer callingUid, Integer resolvedUserId, + ResourceClientProfile profile) { if (mHardware != null) { try { mCallback.onReleased(); @@ -644,6 +657,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { mCallingUid = callingUid; mResolvedUserId = resolvedUserId; mOnFirstFrameCaptured = null; + mResourceClientProfile = profile; if (mHardware != null && mCallback != null) { try { @@ -698,10 +712,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { return mOnFirstFrameCaptured; } + public ResourceClientProfile getResourceClientProfileLocked() { + return mResourceClientProfile; + } + @Override public void binderDied() { synchronized (mLock) { - resetLocked(null, null, null, null, null); + resetLocked(null, null, null, null, null, null); } } @@ -713,6 +731,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { + ", mConfigs: " + Arrays.toString(mConfigs) + ", mCallingUid: " + mCallingUid + ", mResolvedUserId: " + mResolvedUserId + + ", mResourceClientProfile: " + mResourceClientProfile + " }"; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 2f70840cfc8b..41aa4ee65f30 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -18,6 +18,8 @@ package com.android.server.tv.tunerresourcemanager; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.media.tv.TvInputManager; import android.media.tv.tunerresourcemanager.CasSessionRequest; @@ -42,6 +44,7 @@ import com.android.server.SystemService; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -71,7 +74,8 @@ public class TunerResourceManagerService extends SystemService { @GuardedBy("mLock") private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>(); - private TvInputManager mManager; + private TvInputManager mTvInputManager; + private ActivityManager mActivityManager; private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints(); // An internal resource request count to help generate resource handle. @@ -94,7 +98,9 @@ public class TunerResourceManagerService extends SystemService { if (!isForTesting) { publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService()); } - mManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); + mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); + mActivityManager = + (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); mPriorityCongfig.parse(); } @@ -204,7 +210,7 @@ public class TunerResourceManagerService extends SystemService { @Override public boolean requestDemux(@NonNull TunerDemuxRequest request, - @NonNull int[] demuxHandle) throws RemoteException { + @NonNull int[] demuxHandle) throws RemoteException { enforceTunerAccessPermission("requestDemux"); enforceTrmAccessPermission("requestDemux"); if (demuxHandle == null) { @@ -362,14 +368,15 @@ public class TunerResourceManagerService extends SystemService { @Override public boolean isHigherPriority( - ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile) { + ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile) + throws RemoteException { enforceTrmAccessPermission("isHigherPriority"); - if (DEBUG) { - Slog.d(TAG, - "isHigherPriority(challengerProfile=" + challengerProfile - + ", holderProfile=" + challengerProfile + ")"); + if (challengerProfile == null || holderProfile == null) { + throw new RemoteException("Client profiles can't be null."); + } + synchronized (mLock) { + return isHigherPriorityInternal(challengerProfile, holderProfile); } - return true; } } @@ -381,7 +388,7 @@ public class TunerResourceManagerService extends SystemService { } clientId[0] = INVALID_CLIENT_ID; - if (mManager == null) { + if (mTvInputManager == null) { Slog.e(TAG, "TvInputManager is null. Can't register client profile."); return; } @@ -390,7 +397,7 @@ public class TunerResourceManagerService extends SystemService { int pid = profile.getTvInputSessionId() == null ? Binder.getCallingPid() /*callingPid*/ - : mManager.getClientPid(profile.getTvInputSessionId()); /*tvAppId*/ + : mTvInputManager.getClientPid(profile.getTvInputSessionId()); /*tvAppId*/ ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) .tvInputSessionId(profile.getTvInputSessionId()) @@ -693,6 +700,33 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting + protected boolean isHigherPriorityInternal(ResourceClientProfile challengerProfile, + ResourceClientProfile holderProfile) { + if (DEBUG) { + Slog.d(TAG, + "isHigherPriority(challengerProfile=" + challengerProfile + + ", holderProfile=" + challengerProfile + ")"); + } + if (mTvInputManager == null) { + Slog.e(TAG, "TvInputManager is null. Can't compare the priority."); + // Allow the client to acquire the hardware interface + // when the TRM is not able to compare the priority. + return true; + } + + int challengerPid = challengerProfile.getTvInputSessionId() == null + ? Binder.getCallingPid() /*callingPid*/ + : mTvInputManager.getClientPid(challengerProfile.getTvInputSessionId()); /*tvAppId*/ + int holderPid = holderProfile.getTvInputSessionId() == null + ? Binder.getCallingPid() /*callingPid*/ + : mTvInputManager.getClientPid(holderProfile.getTvInputSessionId()); /*tvAppId*/ + + int challengerPriority = getClientPriority(challengerProfile.getUseCase(), challengerPid); + int holderPriority = getClientPriority(holderProfile.getUseCase(), holderPid); + return challengerPriority > holderPriority; + } + + @VisibleForTesting protected void releaseFrontendInternal(FrontendResource fe) { if (DEBUG) { Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")"); @@ -818,8 +852,20 @@ public class TunerResourceManagerService extends SystemService { @VisibleForTesting protected boolean isForeground(int pid) { - // TODO: how to get fg/bg information from pid - return true; + if (mActivityManager == null) { + return false; + } + List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return false; + } + for (RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.pid == pid + && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return true; + } + } + return false; } private void updateFrontendClientMappingOnNewGrant(int grantingId, int ownerClientId) { @@ -1044,7 +1090,7 @@ public class TunerResourceManagerService extends SystemService { } private void enforceTrmAccessPermission(String apiName) { - getContext().enforceCallingPermission("android.permission.TUNER_RESOURCE_ACCESS", + getContext().enforceCallingOrSelfPermission("android.permission.TUNER_RESOURCE_ACCESS", TAG + ": " + apiName); } diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 6734ce76675d..72cdf4afb007 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -21,9 +21,8 @@ import static android.Manifest.permission.FORCE_PERSISTABLE_URI_PERMISSIONS; import static android.Manifest.permission.GET_APP_GRANTED_URI_PERMISSIONS; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; -import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; -import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -1138,8 +1137,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { targetHoldsPermission = false; } - final boolean basicGrant = (modeFlags & ~(FLAG_GRANT_READ_URI_PERMISSION - | FLAG_GRANT_WRITE_URI_PERMISSION)) == 0; + final boolean basicGrant = (modeFlags + & (FLAG_GRANT_PERSISTABLE_URI_PERMISSION | FLAG_GRANT_PREFIX_URI_PERMISSION)) == 0; if (basicGrant && targetHoldsPermission) { // When caller holds permission, and this is a simple permission // grant, we can skip generating any bookkeeping; when any advanced diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f05217c0b47a..e08bfd55011f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1281,12 +1281,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (stack != null && stack.topRunningActivity() == this) { - // carry over the PictureInPictureParams to the parent stack without calling - // TaskOrganizerController#dispatchTaskInfoChanged. - // this is to ensure the stack holding up-to-dated pinned stack information - // when activity is re-parented to enter pip mode, see also - // RootWindowContainer#moveActivityToPinnedStack - stack.mPictureInPictureParams.copyOnlySet(pictureInPictureArgs); // make ensure the TaskOrganizer still works after re-parenting if (firstWindowDrawn) { stack.setHasBeenVisible(true); @@ -7769,6 +7763,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void setPictureInPictureParams(PictureInPictureParams p) { pictureInPictureArgs.copyOnlySet(p); - getTask().getRootTask().setPictureInPictureParams(p); + getTask().getRootTask().onPictureInPictureParamsChanged(); } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index db5e97250cd2..1d7255578759 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -242,12 +242,6 @@ class ActivityStack extends Task { */ boolean mInResumeTopActivity = false; - private boolean mUpdateBoundsDeferred; - private boolean mUpdateBoundsDeferredCalled; - private boolean mUpdateDisplayedBoundsDeferredCalled; - private final Rect mDeferredBounds = new Rect(); - private final Rect mDeferredDisplayedBounds = new Rect(); - int mCurrentUser; /** For comparison with DisplayContent bounds. */ @@ -708,8 +702,10 @@ class ActivityStack extends Task { // Need to make sure windowing mode is supported. If we in the process of creating the stack // no need to resolve the windowing mode again as it is already resolved to the right mode. if (!creating) { - windowingMode = taskDisplayArea.validateWindowingMode(windowingMode, - null /* ActivityRecord */, topTask, getActivityType()); + if (!taskDisplayArea.isValidWindowingMode(windowingMode, null /* ActivityRecord */, + topTask, getActivityType())) { + windowingMode = WINDOWING_MODE_UNDEFINED; + } } if (taskDisplayArea.getRootSplitScreenPrimaryTask() == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { @@ -846,58 +842,6 @@ class ActivityStack extends Task { return getDisplayContent(); } - /** - * Defers updating the bounds of the stack. If the stack was resized/repositioned while - * deferring, the bounds will update in {@link #continueUpdateBounds()}. - */ - void deferUpdateBounds() { - if (!mUpdateBoundsDeferred) { - mUpdateBoundsDeferred = true; - mUpdateBoundsDeferredCalled = false; - } - } - - /** - * Continues updating bounds after updates have been deferred. If there was a resize attempt - * between {@link #deferUpdateBounds()} and {@link #continueUpdateBounds()}, the stack will - * be resized to that bounds. - */ - void continueUpdateBounds() { - if (mUpdateBoundsDeferred) { - mUpdateBoundsDeferred = false; - if (mUpdateBoundsDeferredCalled) { - setTaskBounds(mDeferredBounds); - setBounds(mDeferredBounds); - } - } - } - - private boolean updateBoundsAllowed(Rect bounds) { - if (!mUpdateBoundsDeferred) { - return true; - } - if (bounds != null) { - mDeferredBounds.set(bounds); - } else { - mDeferredBounds.setEmpty(); - } - mUpdateBoundsDeferredCalled = true; - return false; - } - - private boolean updateDisplayedBoundsAllowed(Rect bounds) { - if (!mUpdateBoundsDeferred) { - return true; - } - if (bounds != null) { - mDeferredDisplayedBounds.set(bounds); - } else { - mDeferredDisplayedBounds.setEmpty(); - } - mUpdateDisplayedBoundsDeferredCalled = true; - return false; - } - /** @return true if the stack can only contain one task */ boolean isSingleTaskInstance() { final DisplayContent display = getDisplay(); @@ -2687,10 +2631,6 @@ class ActivityStack extends Task { // TODO: Can only be called from special methods in ActivityStackSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) { - if (!updateBoundsAllowed(displayedBounds)) { - return; - } - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + getRootTaskId()); mAtmService.deferWindowLayout(); try { @@ -2730,10 +2670,6 @@ class ActivityStack extends Task { * basically resizes both stack and task bounds to the same bounds. */ private void setTaskBounds(Rect bounds) { - if (!updateBoundsAllowed(bounds)) { - return; - } - final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds, PooledLambda.__(Task.class), bounds); forAllLeafTasks(c, true /* traverseTopToBottom */); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index ed2153960754..33715207c6ce 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -69,7 +69,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_STACK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_ONLY; import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS; import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; import static com.android.server.wm.RootWindowContainer.TAG_STATES; @@ -125,7 +124,6 @@ import android.os.UserManager; import android.os.WorkSource; import android.provider.MediaStore; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.MergedConfiguration; import android.util.Slog; import android.util.SparseArray; @@ -364,11 +362,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { */ boolean mAppVisibilitiesChangedSinceLastPause; - /** - * Set of tasks that are in resizing mode during an app transition to fill the "void". - */ - private final ArraySet<Integer> mResizingTasksDuringAnimation = new ArraySet<>(); - private KeyguardController mKeyguardController; private PowerManager mPowerManager; @@ -1415,29 +1408,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return mLaunchParamsController; } - private void deferUpdateRecentsHomeStackBounds() { - mRootWindowContainer.deferUpdateBounds(ACTIVITY_TYPE_RECENTS); - mRootWindowContainer.deferUpdateBounds(ACTIVITY_TYPE_HOME); - } - - private void continueUpdateRecentsHomeStackBounds() { - mRootWindowContainer.continueUpdateBounds(ACTIVITY_TYPE_RECENTS); - mRootWindowContainer.continueUpdateBounds(ACTIVITY_TYPE_HOME); - } - - void notifyAppTransitionDone() { - continueUpdateRecentsHomeStackBounds(); - for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) { - final int taskId = mResizingTasksDuringAnimation.valueAt(i); - final Task task = - mRootWindowContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_ONLY); - if (task != null) { - task.setTaskDockedResizing(false); - } - } - mResizingTasksDuringAnimation.clear(); - } - void setSplitScreenResizing(boolean resizing) { if (resizing == mDockedStackResizing) { return; @@ -1470,6 +1440,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mService.deferWindowLayout(); try { stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); + stack.setBounds(null); if (toDisplay.getDisplayId() != stack.getDisplayId()) { stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */); } else { @@ -2471,16 +2442,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } - /** - * Puts a task into resizing mode during the next app transition. - * - * @param task The task to put into resizing mode - */ - void setResizingDuringAnimation(Task task) { - mResizingTasksDuringAnimation.add(task.mTaskId); - task.setTaskDockedResizing(true); - } - int startActivityFromRecents(int callingPid, int callingUid, int taskId, SafeActivityOptions options) { Task task = null; @@ -2508,27 +2469,12 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mService.deferWindowLayout(); try { - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Defer updating the stack in which recents is until the app transition is done, to - // not run into issues where we still need to draw the task in recents but the - // docked stack is already created. - deferUpdateRecentsHomeStackBounds(); - // TODO(task-hierarchy): Remove when tiles are in hierarchy. - // Unset launching windowing mode to prevent creating split-screen-primary stack - // in RWC#anyTaskForId() below. - activityOptions.setLaunchWindowingMode(WINDOWING_MODE_UNDEFINED); - } - task = mRootWindowContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP); if (task == null) { - continueUpdateRecentsHomeStackBounds(); mWindowManager.executeAppTransition(); throw new IllegalArgumentException( "startActivityFromRecents: Task " + taskId + " not found."); - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && task.getWindowingMode() != windowingMode) { - mService.moveTaskToSplitScreenPrimaryTask(task, true /* toTop */); } if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { @@ -2577,12 +2523,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { false /* validateIncomingUser */, null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */); } finally { - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && task != null) { - // If we are launching the task in the docked stack, put it into resizing mode so - // the window renders full-screen with the background filling the void. Also only - // call this at the end to make sure that tasks exists on the window manager side. - setResizingDuringAnimation(task); - } mService.continueWindowLayout(); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 4181f4be30f7..d5df9068e81d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -152,17 +152,6 @@ public abstract class ActivityTaskManagerInternal { IVoiceInteractor mInteractor); /** - * Callback for window manager to let activity manager know that the app transition was - * cancelled. - */ - public abstract void notifyAppTransitionCancelled(); - - /** - * Callback for window manager to let activity manager know that the app transition is finished. - */ - public abstract void notifyAppTransitionFinished(); - - /** * Returns the top activity from each of the currently visible stacks. The first entry will be * the focused activity. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 6a8d5d905a00..36caeecbfec2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6079,20 +6079,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void notifyAppTransitionFinished() { - synchronized (mGlobalLock) { - mStackSupervisor.notifyAppTransitionDone(); - } - } - - @Override - public void notifyAppTransitionCancelled() { - synchronized (mGlobalLock) { - mStackSupervisor.notifyAppTransitionDone(); - } - } - - @Override public List<IBinder> getTopVisibleActivities() { synchronized (mGlobalLock) { return mRootWindowContainer.getTopVisibleActivities(); diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 654ccc80f8a8..67fe9685fd2a 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -166,19 +166,13 @@ public class AppTransitionController { // done behind a dream window. final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); - final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw(); - final ActivityRecord animLpActivity = allowAnimations - ? findAnimLayoutParamsToken(transit, activityTypes) - : null; - final ActivityRecord topOpeningApp = allowAnimations - ? getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */) - : null; - final ActivityRecord topClosingApp = allowAnimations - ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */) - : null; - final ActivityRecord topChangingApp = allowAnimations - ? getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */) - : null; + final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes); + final ActivityRecord topOpeningApp = + getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */); + final ActivityRecord topClosingApp = + getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */); + final ActivityRecord topChangingApp = + getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */); final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 864d96f0fea0..1f10c467e1e6 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2547,6 +2547,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) { final WindowState win = mTapExcludedWindows.get(i); + if (!win.isVisibleLw()) { + continue; + } win.getTouchableRegion(mTmpRegion); mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 541be0a8b580..53b536cf712f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3154,16 +3154,6 @@ public class DisplayPolicy { return 0; } - /** - * Return true if it is okay to perform animations for an app transition - * that is about to occur. You may return false for this if, for example, - * the dream window is currently displayed so the switch should happen - * immediately. - */ - public boolean allowAppAnimationsLw() { - return !mShowingDream; - } - private void requestTransientBars(WindowState swipeTarget) { if (!mService.mPolicy.isUserSetupComplete()) { // Swipe-up for navigation bar is disabled during setup diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8b9e9fe132b7..c93b7354999b 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2170,7 +2170,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final boolean singleActivity = task.getChildCount() == 1; final ActivityStack stack; if (singleActivity) { - stack = r.getRootTask(); + stack = (ActivityStack) task; } else { // In the case of multiple activities, we will create a new task for it and then // move the PIP activity into the task. @@ -2183,6 +2183,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // up-to-dated pinned stack information on this newly created stack. r.reparent(stack, MAX_VALUE, reason); } + if (stack.getParent() != taskDisplayArea) { + // stack is nested, but pinned tasks need to be direct children of their + // display area, so reparent. + stack.reparent(taskDisplayArea, true /* onTop */); + } stack.setWindowingMode(WINDOWING_MODE_PINNED); // Reset the state that indicates it can enter PiP while pausing after we've moved it @@ -2504,20 +2509,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return list; } - void deferUpdateBounds(int activityType) { - final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); - if (stack != null) { - stack.deferUpdateBounds(); - } - } - - void continueUpdateBounds(int activityType) { - final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); - if (stack != null) { - stack.continueUpdateBounds(); - } - } - @Override public void onDisplayAdded(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 44a8daaba1b1..85a31610964e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -82,7 +82,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK; -import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -110,7 +109,6 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; -import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -491,12 +489,6 @@ class Task extends WindowContainer<WindowContainer> { boolean mTaskAppearedSent; /** - * Last Picture-in-Picture params applicable to the task. Updated when the app - * enters Picture-in-Picture or when setPictureInPictureParams is called. - */ - PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); - - /** * This task was created by the task organizer which has the following implementations. * <ul> * <lis>The task won't be removed when it is empty. Removal has to be an explicit request @@ -2013,7 +2005,7 @@ class Task extends WindowContainer<WindowContainer> { } void updateSurfaceSize(SurfaceControl.Transaction transaction) { - if (mSurfaceControl == null || mCreatedByOrganizer) { + if (mSurfaceControl == null || isOrganized()) { return; } @@ -3059,15 +3051,6 @@ class Task extends WindowContainer<WindowContainer> { return mDragResizeMode; } - /** - * Puts this task into docked drag resizing mode. See {@link DragResizeMode}. - * - * @param resizing Whether to put the task into drag resize mode. - */ - public void setTaskDockedResizing(boolean resizing) { - setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER); - } - void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) { if (displayContent == null) { return; @@ -3602,10 +3585,11 @@ class Task extends WindowContainer<WindowContainer> { info.resizeMode = top != null ? top.mResizeMode : mResizeMode; info.topActivityType = top.getActivityType(); - if (mPictureInPictureParams.empty()) { + ActivityRecord rootActivity = top.getRootActivity(); + if (rootActivity == null || rootActivity.pictureInPictureArgs.empty()) { info.pictureInPictureParams = null; } else { - info.pictureInPictureParams = mPictureInPictureParams; + info.pictureInPictureParams = rootActivity.pictureInPictureArgs; } info.topActivityInfo = mReuseActivitiesReport.top != null ? mReuseActivitiesReport.top.info @@ -4521,8 +4505,7 @@ class Task extends WindowContainer<WindowContainer> { updateShadowsRadius(hasFocus, getPendingTransaction()); } - void setPictureInPictureParams(PictureInPictureParams p) { - mPictureInPictureParams.copyOnlySet(p); + void onPictureInPictureParamsChanged() { if (isOrganized()) { mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 0a1ee2b79711..37a4c1f6849b 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -1333,16 +1332,16 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } /** - * Check that the requested windowing-mode is appropriate for the specified task and/or activity + * Check if the requested windowing-mode is appropriate for the specified task and/or activity * on this display. * * @param windowingMode The windowing-mode to validate. * @param r The {@link ActivityRecord} to check against. * @param task The {@link Task} to check against. * @param activityType An activity type. - * @return The provided windowingMode or the closest valid mode which is appropriate. + * @return {@code true} if windowingMode is valid, {@code false} otherwise. */ - int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task, + boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task, int activityType) { // Make sure the windowing mode we are trying to use makes sense for what is supported. boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow; @@ -1362,24 +1361,35 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } + return windowingMode != WINDOWING_MODE_UNDEFINED + && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, + supportsFreeform, supportsPip, activityType); + } + + /** + * Check that the requested windowing-mode is appropriate for the specified task and/or activity + * on this display. + * + * @param windowingMode The windowing-mode to validate. + * @param r The {@link ActivityRecord} to check against. + * @param task The {@link Task} to check against. + * @param activityType An activity type. + * @return The provided windowingMode or the closest valid mode which is appropriate. + */ + int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task, + int activityType) { final boolean inSplitScreenMode = isSplitScreenModeActivated(); - if (!inSplitScreenMode - && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { + if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { // Switch to the display's windowing mode if we are not in split-screen mode and we are // trying to launch in split-screen secondary. windowingMode = WINDOWING_MODE_UNDEFINED; - } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_UNDEFINED) - && supportsSplitScreen) { + } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; } - - if (windowingMode != WINDOWING_MODE_UNDEFINED - && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, - supportsFreeform, supportsPip, activityType)) { - return windowingMode; + if (!isValidWindowingMode(windowingMode, r, task, activityType)) { + return WINDOWING_MODE_UNDEFINED; } - return WINDOWING_MODE_UNDEFINED; + return windowingMode; } boolean isTopStack(ActivityStack stack) { diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 79baab6bfbb6..06c2b1687fa4 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -48,6 +48,16 @@ public class TaskTapPointerEventListener implements PointerEventListener { mDisplayContent = displayContent; } + private void restorePointerIcon(int x, int y) { + if (mPointerIconType != TYPE_NOT_SPECIFIED) { + mPointerIconType = TYPE_NOT_SPECIFIED; + // Find the underlying window and ask it to restore the pointer icon. + mService.mH.removeMessages(H.RESTORE_POINTER_ICON); + mService.mH.obtainMessage(H.RESTORE_POINTER_ICON, + x, y, mDisplayContent).sendToTarget(); + } + } + @Override public void onPointerEvent(MotionEvent motionEvent) { switch (motionEvent.getActionMasked()) { @@ -67,6 +77,10 @@ public class TaskTapPointerEventListener implements PointerEventListener { case MotionEvent.ACTION_HOVER_MOVE: { final int x = (int) motionEvent.getX(); final int y = (int) motionEvent.getY(); + if (mTouchExcludeRegion.contains(x, y)) { + restorePointerIcon(x, y); + break; + } final Task task = mDisplayContent.findTaskForResizePoint(x, y); int iconType = TYPE_NOT_SPECIFIED; if (task != null) { @@ -103,13 +117,7 @@ public class TaskTapPointerEventListener implements PointerEventListener { case MotionEvent.ACTION_HOVER_EXIT: { final int x = (int) motionEvent.getX(); final int y = (int) motionEvent.getY(); - if (mPointerIconType != TYPE_NOT_SPECIFIED) { - mPointerIconType = TYPE_NOT_SPECIFIED; - // Find the underlying window and ask it to restore the pointer icon. - mService.mH.removeMessages(H.RESTORE_POINTER_ICON); - mService.mH.obtainMessage(H.RESTORE_POINTER_ICON, - x, y, mDisplayContent).sendToTarget(); - } + restorePointerIcon(x, y); } break; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 07840b5163a4..ae0c9b15689a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -72,6 +72,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; +import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManagerGlobal.ADD_OKAY; @@ -926,8 +927,14 @@ public class WindowManagerService extends IWindowManager.Stub } void updateFixedRotationTransform() { - mIsFixedRotationTransformEnabled = Settings.Global.getInt(mContext.getContentResolver(), - FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + final int enabled = Settings.Global.getInt(mContext.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 2); + if (enabled == 2) { + // Make sure who read the settings won't use inconsistent default value. + Settings.Global.putInt(mContext.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 1); + } + mIsFixedRotationTransformEnabled = enabled != 0; } } @@ -1057,12 +1064,10 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void onAppTransitionCancelledLocked(int transit) { - mAtmInternal.notifyAppTransitionCancelled(); } @Override public void onAppTransitionFinishedLocked(IBinder token) { - mAtmInternal.notifyAppTransitionFinished(); final ActivityRecord atoken = mRoot.getActivityRecord(token); if (atoken == null) { return; @@ -1367,6 +1372,7 @@ public class WindowManagerService extends IWindowManager.Stub case TYPE_NOTIFICATION_SHADE: case TYPE_NAVIGATION_BAR: case TYPE_INPUT_METHOD_DIALOG: + case TYPE_VOLUME_OVERLAY: return true; } return false; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 0e83beed6b90..c570cf1d949f 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -846,6 +846,23 @@ class WindowStateAnimator { } } + private boolean shouldConsumeMainWindowSizeTransaction() { + // We only consume the transaction when the client is calling relayout + // because this is the only time we know the frameNumber will be valid + // due to the client renderer being paused. Put otherwise, only when + // mInRelayout is true can we guarantee the next frame will contain + // the most recent configuration. + if (!mWin.mInRelayout) return false; + // Since we can only do this for one window, we focus on the main application window + if (mAttrType != TYPE_BASE_APPLICATION) return false; + final Task task = mWin.getTask(); + if (task == null) return false; + if (task.getMainWindowSizeChangeTransaction() == null) return false; + // Likewise we only focus on the task root, since we can only use one window + if (!mWin.mActivityRecord.isRootOfTask()) return false; + return true; + } + void setSurfaceBoundariesLocked(final boolean recoveringMemory) { if (mSurfaceController == null) { return; @@ -886,8 +903,9 @@ class WindowStateAnimator { clipRect = mTmpClipRect; } - if (w.mInRelayout && (mAttrType == TYPE_BASE_APPLICATION) && (task != null) - && (task.getMainWindowSizeChangeTransaction() != null)) { + if (shouldConsumeMainWindowSizeTransaction()) { + task.getSurfaceControl().deferTransactionUntil(mWin.getClientViewRootSurface(), + mWin.getFrameNumber()); mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(), mWin.getFrameNumber()); SurfaceControl.mergeToGlobalTransaction(task.getMainWindowSizeChangeTransaction()); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 9bc5d34c11af..20139451e4b9 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -237,27 +237,28 @@ public: /* --- InputDispatcherPolicyInterface implementation --- */ virtual void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, - uint32_t policyFlags); + uint32_t policyFlags) override; virtual void notifyConfigurationChanged(nsecs_t when); - virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<IBinder>& token, - const std::string& reason); + virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>& inputApplicationHandle, + const sp<IBinder>& token, const std::string& reason) override; virtual void notifyInputChannelBroken(const sp<IBinder>& token); - virtual void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken); - virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); - virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig); - virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); + virtual void notifyFocusChanged(const sp<IBinder>& oldToken, + const sp<IBinder>& newToken) override; + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override; + virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override; + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, + uint32_t& policyFlags) override; virtual void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when, - uint32_t& policyFlags); - virtual nsecs_t interceptKeyBeforeDispatching( - const sp<IBinder>& token, - const KeyEvent* keyEvent, uint32_t policyFlags); - virtual bool dispatchUnhandledKey(const sp<IBinder>& token, - const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent); - virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType); - virtual bool checkInjectEventsPermissionNonReentrant( - int32_t injectorPid, int32_t injectorUid); - virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken); + uint32_t& policyFlags) override; + virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, + const KeyEvent* keyEvent, + uint32_t policyFlags) override; + virtual bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent, + uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override; + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) override; + virtual bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, + int32_t injectorUid) override; + virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; /* --- PointerControllerPolicyInterface implementation --- */ @@ -692,9 +693,8 @@ static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env, return handle->getInputApplicationHandleObjLocalRef(env); } - -nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<IBinder>& token, const std::string& reason) { +nsecs_t NativeInputManager::notifyAnr(const sp<InputApplicationHandle>& inputApplicationHandle, + const sp<IBinder>& token, const std::string& reason) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyANR"); #endif @@ -1453,9 +1453,13 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */, return INPUT_EVENT_INJECTION_FAILED; } - return (jint) im->getInputManager()->getDispatcher()->injectInputEvent( - & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis, - uint32_t(policyFlags)); + const int32_t result = + im->getInputManager()->getDispatcher()->injectInputEvent(&keyEvent, injectorPid, + injectorUid, syncMode, + std::chrono::milliseconds( + timeoutMillis), + uint32_t(policyFlags)); + return static_cast<jint>(result); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj); if (!motionEvent) { @@ -1463,9 +1467,13 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */, return INPUT_EVENT_INJECTION_FAILED; } - return (jint) im->getInputManager()->getDispatcher()->injectInputEvent( - motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis, - uint32_t(policyFlags)); + const int32_t result = + (jint)im->getInputManager() + ->getDispatcher() + ->injectInputEvent(motionEvent, injectorPid, injectorUid, syncMode, + std::chrono::milliseconds(timeoutMillis), + uint32_t(policyFlags)); + return static_cast<jint>(result); } else { jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3323fa4b53e3..966694ad346c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4567,9 +4567,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (isProfileOwner(adminReceiver, userHandle)) { if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) { + UserHandle parentUserHandle = UserHandle.of(getProfileParentId(userHandle)); mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - false, - UserHandle.of(getProfileParentId(userHandle))); + false, parentUserHandle); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, + false, parentUserHandle); } final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle, /* parent */ false); @@ -7213,6 +7215,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManager.setUserRestriction( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false, UserHandle.SYSTEM); + mUserManager.setUserRestriction( + UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM); // Device-wide policies set by the profile owner need to be cleaned up here. mLockPatternUtils.setDeviceOwnerInfo(null); @@ -13825,6 +13829,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true, parentUser); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, + parentUser); }); // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0fc333f4b38c..fa3f33067e8e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1891,6 +1891,10 @@ public final class SystemServer { || mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { t.traceBegin("StartTvInputManager"); mSystemServiceManager.startService(TvInputManagerService.class); + t.traceEnd(); + } + + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TUNER)) { t.traceBegin("StartTunerResourceManager"); mSystemServiceManager.startService(TunerResourceManagerService.class); t.traceEnd(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 6b36bc591b78..ed40fe756ea1 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1998,7 +1998,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS = Sets.newSet( UserManager.DISALLOW_CONFIG_DATE_TIME, - UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_BLUETOOTH_SHARING, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, @@ -4005,6 +4004,12 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Any caller should be able to call this method. assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile()); configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); + + verify(getServices().userManager).setUserRestriction( + eq(UserManager.DISALLOW_ADD_USER), + eq(true), + eq(UserHandle.of(UserHandle.USER_SYSTEM))); + assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile()); // A random caller from another user should also be able to get the right result. @@ -4012,6 +4017,35 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile()); } + public void testMarkOrganizationOwnedDevice_baseRestrictionsAdded() throws Exception { + addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1); + + configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); + + // Base restriction DISALLOW_REMOVE_MANAGED_PROFILE added + verify(getServices().userManager).setUserRestriction( + eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), + eq(true), + eq(UserHandle.of(UserHandle.USER_SYSTEM))); + + // Base restriction DISALLOW_ADD_USER added + verify(getServices().userManager).setUserRestriction( + eq(UserManager.DISALLOW_ADD_USER), + eq(true), + eq(UserHandle.of(UserHandle.USER_SYSTEM))); + + // Assert base restrictions cannot be added or removed by admin + assertExpectException(SecurityException.class, null, () -> + parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)); + assertExpectException(SecurityException.class, null, () -> + parentDpm.clearUserRestriction(admin1, + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)); + assertExpectException(SecurityException.class, null, () -> + parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER)); + assertExpectException(SecurityException.class, null, () -> + parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER)); + } + public void testSetTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index d4edab44bae3..63d797e9b95c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -178,6 +178,7 @@ public class PackageInstallerSessionTest { /* files */ null, /* prepared */ true, /* committed */ true, + /* destroyed */ staged ? true : false, /* sealed */ false, // Setting to true would trigger some PM logic. /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0], /* parentSessionId */ parentSessionId, diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index daaf870fa695..b0b5386a49bd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -154,18 +154,7 @@ public class PackageParserTest { @Test public void test_serializePackage() throws Exception { - try (PackageParser2 pp = new PackageParser2(null, false, null, mTmpDir, - new PackageParser2.Callback() { - @Override - public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { - return true; - } - - @Override - public boolean hasFeature(String feature) { - return false; - } - })) { + try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) { ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index 3888ff3e278a..caa8ae5e0e39 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -28,8 +28,13 @@ import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.dex.DexMetadataHelper; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.os.FileUtils; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -208,9 +213,12 @@ public class DexMetadataHelperTest { throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); File dm = createDexMetadataFile("install_split_base.apk"); - PackageParser.PackageLite pkg = new PackageParser().parsePackageLite(mTmpDir, - 0 /* flags */); - + ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */); + if (result.isError()) { + throw new IllegalStateException(result.getErrorMessage(), result.getException()); + } + PackageParser.PackageLite pkg = result.getResult(); Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg)); } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index 6de08fd1251f..086c845fa4b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -30,10 +30,8 @@ import android.content.pm.PermissionInfo import android.content.pm.ProviderInfo import android.os.Debug import android.os.Environment -import android.os.ServiceManager import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry -import com.android.internal.compat.IPlatformCompat import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageSetting import com.android.server.pm.parsing.pkg.AndroidPackage @@ -63,27 +61,7 @@ open class AndroidPackageParsingTestBase { setCallback { false /* hasFeature */ } } - private val platformCompat = IPlatformCompat.Stub - .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)) - - protected val packageParser2 = PackageParser2(null /* separateProcesses */, - false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */, - object : PackageParser2.Callback() { - override fun isChangeEnabled( - changeId: Long, - appInfo: ApplicationInfo - ): Boolean { - // This test queries PlatformCompat because prebuilts in the tree - // may not be updated to be compliant with the latest enforcement checks. - return platformCompat.isChangeEnabled(changeId, appInfo) - } - - // Assume the device doesn't support anything. This will affect permission - // parsing and will force <uses-permission/> declarations to include all - // requiredNotFeature permissions and exclude all requiredFeature permissions. - // This mirrors the old behavior. - override fun hasFeature(feature: String) = false - }) + protected val packageParser2 = PackageParser2.forParsingFileWithDefaults() /** * It would be difficult to mock all possibilities, so just use the APKs on device. diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 939b7a0beb49..bb223b33d8aa 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -29,8 +29,12 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; +import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.component.ParsedComponent; import android.content.pm.parsing.component.ParsedPermission; +import android.content.pm.parsing.result.ParseResult; import android.os.Build; import android.os.Bundle; import android.os.FileUtils; @@ -518,10 +522,15 @@ public class PackageParserLegacyCoreTest { apexInfo.versionCode = 191000070; int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES; - PackageParser pp = new PackageParser(); - PackageParser.Package p = pp.parsePackage(apexFile, flags, false); - PackageParser.collectCertificates(p, false); - PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags); + ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile, + flags, false /*collectCertificates*/); + if (result.isError()) { + throw new IllegalStateException(result.getErrorMessage(), result.getException()); + } + + ParsingPackage pkg = result.getResult(); + pkg.setSigningDetails(ParsingPackageUtils.getSigningDetails(pkg, false)); + PackageInfo pi = PackageInfoWithoutStateUtils.generate(pkg, apexInfo, flags); assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertTrue(pi.applicationInfo.enabled); diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index 22487071cd71..d8910dec0bcc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -64,20 +64,6 @@ class PackageParsingDeferErrorTest { } } - private val parsingCallback = object : ParsingPackageUtils.Callback { - override fun hasFeature(feature: String?) = true - - override fun startParsingPackage( - packageName: String, - baseCodePath: String, - codePath: String, - manifestArray: TypedArray, - isCoreApp: Boolean - ): ParsingPackage { - return ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray) - } - } - @get:Rule val tempFolder = TemporaryFolder(context.filesDir) @@ -144,6 +130,7 @@ class PackageParsingDeferErrorTest { input.copyTo(output) } } - return ParsingPackageUtils.parseDefaultOneTime(file, 0, inputCallback, parsingCallback) + return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, + false /*collectCertificates*/) } } diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 21af3563b869..f9343236662b 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -64,6 +64,7 @@ public class TunerResourceManagerServiceTest { private Context mContextSpy; @Mock private ITvInputManager mITvInputManagerMock; private TunerResourceManagerService mTunerResourceManagerService; + private boolean mIsForeground; private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub { boolean mReclaimed; @@ -104,7 +105,12 @@ public class TunerResourceManagerServiceTest { TvInputManager tvInputManager = new TvInputManager(mITvInputManagerMock, 0); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); when(mContextSpy.getSystemService(Context.TV_INPUT_SERVICE)).thenReturn(tvInputManager); - mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy); + mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy) { + @Override + protected boolean isForeground(int pid) { + return mIsForeground; + } + }; mTunerResourceManagerService.onStart(true /*isForTesting*/); } @@ -737,4 +743,22 @@ public class TunerResourceManagerServiceTest { .isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0); } + + @Test + public void isHigherPriorityTest() { + mIsForeground = false; + ResourceClientProfile backgroundPlaybackProfile = + new ResourceClientProfile(null /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + ResourceClientProfile backgroundRecordProfile = + new ResourceClientProfile(null /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD); + int backgroundPlaybackPriority = mTunerResourceManagerService.getClientPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 0); + int backgroundRecordPriority = mTunerResourceManagerService.getClientPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 0); + assertThat(mTunerResourceManagerService.isHigherPriorityInternal(backgroundPlaybackProfile, + backgroundRecordProfile)).isEqualTo( + (backgroundPlaybackPriority > backgroundRecordPriority)); + } } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index 0e48e7ed2682..e86399e1a631 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -171,7 +171,7 @@ public class UriGrantsManagerServiceTest { final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/"); { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) - .addFlags(FLAG_READ); + .addFlags(FLAG_READ | Intent.FLAG_ACTIVITY_CLEAR_TASK); assertNull(mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY)); } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 39062f017a73..6718db768fdb 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -366,29 +366,87 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime, false)); } + private static class TestParoleListener extends AppIdleStateChangeListener { + private boolean mIsParoleOn = false; + private CountDownLatch mLatch; + private boolean mIsExpecting = false; + private boolean mExpectedParoleState; + + boolean getParoleState() { + synchronized (this) { + return mIsParoleOn; + } + } + + void rearmLatch(boolean expectedParoleState) { + synchronized (this) { + mLatch = new CountDownLatch(1); + mIsExpecting = true; + mExpectedParoleState = expectedParoleState; + } + } + + void awaitOnLatch(long time) throws Exception { + mLatch.await(time, TimeUnit.MILLISECONDS); + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, + int bucket, int reason) { + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + synchronized (this) { + // Only record information if it is being looked for + if (mLatch != null && mLatch.getCount() > 0) { + mIsParoleOn = isParoleOn; + if (mIsExpecting && isParoleOn == mExpectedParoleState) { + mLatch.countDown(); + } + } + } + } + } + @Test public void testIsAppIdle_Charging() throws Exception { + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + setChargingState(mController, false); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertFalse(mController.isInParole()); + paroleListener.rearmLatch(true); setChargingState(mController, true); + paroleListener.awaitOnLatch(2000); + assertTrue(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertTrue(mController.isInParole()); + paroleListener.rearmLatch(false); setChargingState(mController, false); + paroleListener.awaitOnLatch(2000); + assertFalse(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertFalse(mController.isInParole()); } @Test public void testIsAppIdle_Enabled() throws Exception { setChargingState(mController, false); + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + setAppIdleEnabled(mController, true); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); @@ -396,11 +454,17 @@ public class AppStandbyControllerTests { assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + paroleListener.rearmLatch(false); setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(2000); + assertTrue(paroleListener.mIsParoleOn); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + paroleListener.rearmLatch(true); setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(2000); + assertFalse(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index 3c2d55058c3e..182bf949af1f 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -41,6 +41,7 @@ public class UiServiceTestCase { protected static final String PKG_N_MR1 = "com.example.n_mr1"; protected static final String PKG_O = "com.example.o"; protected static final String PKG_P = "com.example.p"; + protected static final String PKG_R = "com.example.r"; @Rule public final TestableContext mContext = @@ -69,6 +70,8 @@ public class UiServiceTestCase { return Build.VERSION_CODES.O; case PKG_P: return Build.VERSION_CODES.P; + case PKG_R: + return Build.VERSION_CODES.R; default: return Build.VERSION_CODES.CUR_DEVELOPMENT; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index babe80e4b612..289933e5ecb2 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -250,6 +250,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int NOTIFICATION_LOCATION_UNKNOWN = 0; + private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; + @Mock private NotificationListeners mListeners; @Mock private NotificationAssistants mAssistants; @@ -471,6 +473,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mShortcutHelper.setLauncherApps(mLauncherApps); mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); + // Pretend the shortcut exists + List<ShortcutInfo> shortcutInfos = new ArrayList<>(); + ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(PKG); + when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); + when(info.getUserId()).thenReturn(USER_SYSTEM); + when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); + shortcutInfos.add(info); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + // Set the testable bubble extractor RankingHelper rankingHelper = mService.getRankingHelper(); BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class); @@ -704,6 +719,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ) .setActions(replyAction) .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setShortcutId(VALID_CONVO_SHORTCUT_ID) .setGroupSummary(isSummary); if (groupKey != null) { nb.setGroup(groupKey); @@ -6100,7 +6116,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationBubbles_flagRemoved_whenShortcutRemoved() throws RemoteException { - final String shortcutId = "someshortcutId"; setUpPrefsForBubbles(PKG, mUid, true /* global */, BUBBLE_PREFERENCE_ALL /* app */, @@ -6111,27 +6126,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Messaging notification with shortcut info Notification.BubbleMetadata metadata = - new Notification.BubbleMetadata.Builder(shortcutId).build(); + new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build(); Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, null /* groupKey */, false /* isSummary */); - nb.setShortcutId(shortcutId); + nb.setShortcutId(VALID_CONVO_SHORTCUT_ID); nb.setBubbleMetadata(metadata); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - // Pretend the shortcut exists - List<ShortcutInfo> shortcutInfos = new ArrayList<>(); - ShortcutInfo info = mock(ShortcutInfo.class); - when(info.getPackage()).thenReturn(PKG); - when(info.getId()).thenReturn(shortcutId); - when(info.getUserId()).thenReturn(USER_SYSTEM); - when(info.isLongLived()).thenReturn(true); - when(info.isEnabled()).thenReturn(true); - shortcutInfos.add(info); - when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); - when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), - anyString(), anyInt(), any())).thenReturn(true); + // Test: Send the bubble notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), @@ -6149,7 +6153,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Make sure the shortcut is cached. verify(mShortcutServiceInternal).cacheShortcuts( - anyInt(), any(), eq(PKG), eq(Collections.singletonList(shortcutId)), + anyInt(), any(), eq(PKG), eq(Collections.singletonList(VALID_CONVO_SHORTCUT_ID)), eq(USER_SYSTEM)); // Test: Remove the shortcut @@ -6613,6 +6617,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { convo2.setNotificationChannel(channel2); convos.add(convo2); when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); List<ConversationChannelWrapper> conversations = mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); @@ -6640,6 +6645,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "testRecordMessages_invalidMsg"); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); @@ -6660,17 +6666,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - // Pretend the shortcut exists - List<ShortcutInfo> shortcutInfos = new ArrayList<>(); - ShortcutInfo info = mock(ShortcutInfo.class); - when(info.getPackage()).thenReturn(PKG); - when(info.getId()).thenReturn("id"); - when(info.getUserId()).thenReturn(USER_SYSTEM); - when(info.isLongLived()).thenReturn(true); - when(info.isEnabled()).thenReturn(true); - shortcutInfos.add(info); - when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 9f593ce42741..b03596a35c32 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -39,7 +39,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.Notification; @@ -90,7 +89,7 @@ public class NotificationRecordTest extends UiServiceTestCase { @Mock private PackageManager mPm; @Mock private ContentResolver mContentResolver; - private final String pkg = PKG_N_MR1; + private final String mPkg = PKG_O; private final int uid = 9583; private final int id1 = 1; private final String tag1 = "tag1"; @@ -198,10 +197,14 @@ public class NotificationRecordTest extends UiServiceTestCase { } Notification n = builder.build(); - return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid); + return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid); } private StatusBarNotification getMessagingStyleNotification() { + return getMessagingStyleNotification(mPkg); + } + + private StatusBarNotification getMessagingStyleNotification(String pkg) { final Builder builder = new Builder(mMockContext) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon); @@ -658,7 +661,7 @@ public class NotificationRecordTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); - record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId())); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); record.applyAdjustments(); @@ -687,7 +690,7 @@ public class NotificationRecordTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); - record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId())); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); record.applyAdjustments(); assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment()); @@ -705,7 +708,7 @@ public class NotificationRecordTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); - record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId())); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); record.applyAdjustments(); @@ -1134,6 +1137,15 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test + public void testIsConversation_noShortcut_targetsR() { + StatusBarNotification sbn = getMessagingStyleNotification(PKG_R); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + record.setShortcutInfo(null); + + assertFalse(record.isConversation()); + } + + @Test public void testIsConversation_channelDemoted() { StatusBarNotification sbn = getMessagingStyleNotification(); channel.setDemoted(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 2ea58a028a0a..fdc5c7bf0ce1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -836,7 +836,7 @@ public class WindowOrganizerTests extends WindowTestsBase { spyOn(record); doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean()); - record.getRootTask().setHasBeenVisible(true); + record.getTask().setHasBeenVisible(true); return record; } diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index a716b37f7efd..56cba1d3f913 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -2102,12 +2102,12 @@ public abstract class ConnectionService extends Service { private void abort(String callId) { - Log.d(this, "abort %s", callId); + Log.i(this, "abort %s", callId); findConnectionForAction(callId, "abort").onAbort(); } private void answerVideo(String callId, int videoState) { - Log.d(this, "answerVideo %s", callId); + Log.i(this, "answerVideo %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "answer").onAnswer(videoState); } else { @@ -2116,7 +2116,7 @@ public abstract class ConnectionService extends Service { } private void answer(String callId) { - Log.d(this, "answer %s", callId); + Log.i(this, "answer %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "answer").onAnswer(); } else { @@ -2125,12 +2125,12 @@ public abstract class ConnectionService extends Service { } private void deflect(String callId, Uri address) { - Log.d(this, "deflect %s", callId); + Log.i(this, "deflect %s", callId); findConnectionForAction(callId, "deflect").onDeflect(address); } private void reject(String callId) { - Log.d(this, "reject %s", callId); + Log.i(this, "reject %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "reject").onReject(); } else { @@ -2139,34 +2139,34 @@ public abstract class ConnectionService extends Service { } private void reject(String callId, String rejectWithMessage) { - Log.d(this, "reject %s with message", callId); + Log.i(this, "reject %s with message", callId); findConnectionForAction(callId, "reject").onReject(rejectWithMessage); } private void reject(String callId, @android.telecom.Call.RejectReason int rejectReason) { - Log.d(this, "reject %s with reason %d", callId, rejectReason); + Log.i(this, "reject %s with reason %d", callId, rejectReason); findConnectionForAction(callId, "reject").onReject(rejectReason); } private void transfer(String callId, Uri number, boolean isConfirmationRequired) { - Log.d(this, "transfer %s", callId); + Log.i(this, "transfer %s", callId); findConnectionForAction(callId, "transfer").onTransfer(number, isConfirmationRequired); } private void consultativeTransfer(String callId, String otherCallId) { - Log.d(this, "consultativeTransfer %s", callId); + Log.i(this, "consultativeTransfer %s", callId); Connection connection1 = findConnectionForAction(callId, "consultativeTransfer"); Connection connection2 = findConnectionForAction(otherCallId, " consultativeTransfer"); connection1.onTransfer(connection2); } private void silence(String callId) { - Log.d(this, "silence %s", callId); + Log.i(this, "silence %s", callId); findConnectionForAction(callId, "silence").onSilence(); } private void disconnect(String callId) { - Log.d(this, "disconnect %s", callId); + Log.i(this, "disconnect %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "disconnect").onDisconnect(); } else { @@ -2175,7 +2175,7 @@ public abstract class ConnectionService extends Service { } private void hold(String callId) { - Log.d(this, "hold %s", callId); + Log.i(this, "hold %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "hold").onHold(); } else { @@ -2184,7 +2184,7 @@ public abstract class ConnectionService extends Service { } private void unhold(String callId) { - Log.d(this, "unhold %s", callId); + Log.i(this, "unhold %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "unhold").onUnhold(); } else { @@ -2193,7 +2193,7 @@ public abstract class ConnectionService extends Service { } private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { - Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); + Log.i(this, "onAudioStateChanged %s %s", callId, callAudioState); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( callAudioState); @@ -2204,7 +2204,7 @@ public abstract class ConnectionService extends Service { } private void playDtmfTone(String callId, char digit) { - Log.d(this, "playDtmfTone %s %c", callId, digit); + Log.i(this, "playDtmfTone %s %c", callId, digit); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); } else { @@ -2213,7 +2213,7 @@ public abstract class ConnectionService extends Service { } private void stopDtmfTone(String callId) { - Log.d(this, "stopDtmfTone %s", callId); + Log.i(this, "stopDtmfTone %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); } else { @@ -2222,7 +2222,7 @@ public abstract class ConnectionService extends Service { } private void conference(String callId1, String callId2) { - Log.d(this, "conference %s, %s", callId1, callId2); + Log.i(this, "conference %s, %s", callId1, callId2); // Attempt to get second connection or conference. Connection connection2 = findConnectionForAction(callId2, "conference"); @@ -2269,7 +2269,7 @@ public abstract class ConnectionService extends Service { } private void splitFromConference(String callId) { - Log.d(this, "splitFromConference(%s)", callId); + Log.i(this, "splitFromConference(%s)", callId); Connection connection = findConnectionForAction(callId, "splitFromConference"); if (connection == getNullConnection()) { @@ -2284,7 +2284,7 @@ public abstract class ConnectionService extends Service { } private void mergeConference(String callId) { - Log.d(this, "mergeConference(%s)", callId); + Log.i(this, "mergeConference(%s)", callId); Conference conference = findConferenceForAction(callId, "mergeConference"); if (conference != null) { conference.onMerge(); @@ -2292,7 +2292,7 @@ public abstract class ConnectionService extends Service { } private void swapConference(String callId) { - Log.d(this, "swapConference(%s)", callId); + Log.i(this, "swapConference(%s)", callId); Conference conference = findConferenceForAction(callId, "swapConference"); if (conference != null) { conference.onSwap(); @@ -2300,7 +2300,7 @@ public abstract class ConnectionService extends Service { } private void addConferenceParticipants(String callId, List<Uri> participants) { - Log.d(this, "addConferenceParticipants(%s)", callId); + Log.i(this, "addConferenceParticipants(%s)", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "addConferenceParticipants") .onAddConferenceParticipants(participants); @@ -2318,7 +2318,7 @@ public abstract class ConnectionService extends Service { * @param callId The ID of the call to pull. */ private void pullExternalCall(String callId) { - Log.d(this, "pullExternalCall(%s)", callId); + Log.i(this, "pullExternalCall(%s)", callId); Connection connection = findConnectionForAction(callId, "pullExternalCall"); if (connection != null) { connection.onPullExternalCall(); @@ -2335,7 +2335,7 @@ public abstract class ConnectionService extends Service { * @param extras Extras associated with the event. */ private void sendCallEvent(String callId, String event, Bundle extras) { - Log.d(this, "sendCallEvent(%s, %s)", callId, event); + Log.i(this, "sendCallEvent(%s, %s)", callId, event); Connection connection = findConnectionForAction(callId, "sendCallEvent"); if (connection != null) { connection.onCallEvent(event, extras); @@ -2348,7 +2348,7 @@ public abstract class ConnectionService extends Service { * @param callId The ID of the call which completed handover. */ private void notifyHandoverComplete(String callId) { - Log.d(this, "notifyHandoverComplete(%s)", callId); + Log.i(this, "notifyHandoverComplete(%s)", callId); Connection connection = findConnectionForAction(callId, "notifyHandoverComplete"); if (connection != null) { connection.onHandoverComplete(); @@ -2368,7 +2368,7 @@ public abstract class ConnectionService extends Service { * @param extras The new extras bundle. */ private void handleExtrasChanged(String callId, Bundle extras) { - Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras); + Log.i(this, "handleExtrasChanged(%s, %s)", callId, extras); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras); } else if (mConferenceById.containsKey(callId)) { @@ -2377,7 +2377,7 @@ public abstract class ConnectionService extends Service { } private void startRtt(String callId, Connection.RttTextStream rttTextStream) { - Log.d(this, "startRtt(%s)", callId); + Log.i(this, "startRtt(%s)", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream); } else if (mConferenceById.containsKey(callId)) { @@ -2386,7 +2386,7 @@ public abstract class ConnectionService extends Service { } private void stopRtt(String callId) { - Log.d(this, "stopRtt(%s)", callId); + Log.i(this, "stopRtt(%s)", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "stopRtt").onStopRtt(); } else if (mConferenceById.containsKey(callId)) { @@ -2395,7 +2395,7 @@ public abstract class ConnectionService extends Service { } private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) { - Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null); + Log.i(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "handleRttUpgradeResponse") .handleRttUpgradeResponse(rttTextStream); @@ -2405,7 +2405,7 @@ public abstract class ConnectionService extends Service { } private void onPostDialContinue(String callId, boolean proceed) { - Log.d(this, "onPostDialContinue(%s)", callId); + Log.i(this, "onPostDialContinue(%s)", callId); findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); } diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 4f6a9d6450f8..a90d0532b721 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -16,7 +16,9 @@ package android.telecom; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; import android.net.Uri; import android.os.Build; @@ -29,8 +31,10 @@ import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import java.util.Arrays; import java.util.IllegalFormatException; import java.util.Locale; +import java.util.stream.Collectors; /** * Manages logging for the entire module. @@ -212,6 +216,16 @@ public class Log { return getSessionManager().getExternalSession(); } + /** + * Retrieves external session information, providing a context for the recipient of the session + * info where the external session came from. + * @param ownerInfo The external owner info. + * @return New {@link Session.Info} instance with owner info set. + */ + public static Session.Info getExternalSession(@NonNull String ownerInfo) { + return getSessionManager().getExternalSession(ownerInfo); + } + public static void cancelSubsession(Session subsession) { getSessionManager().cancelSubsession(subsession); } @@ -481,4 +495,34 @@ public class Log { } return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix); } + + /** + * Generates an abbreviated version of the package name from a component. + * E.g. com.android.phone becomes cap + * @param componentName The component name to abbreviate. + * @return Abbreviation of empty string if component is null. + * @hide + */ + public static String getPackageAbbreviation(ComponentName componentName) { + if (componentName == null) { + return ""; + } + return getPackageAbbreviation(componentName.getPackageName()); + } + + /** + * Generates an abbreviated version of the package name. + * E.g. com.android.phone becomes cap + * @param packageName The packageName name to abbreviate. + * @return Abbreviation of empty string if package is null. + * @hide + */ + public static String getPackageAbbreviation(String packageName) { + if (packageName == null) { + return ""; + } + return Arrays.stream(packageName.split("\\.")) + .map(s -> s.substring(0,1)) + .collect(Collectors.joining("")); + } } diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index 8d3f4e1df8bc..4aa3614fa004 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -17,6 +17,7 @@ package android.telecom.Logging; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.telecom.Log; @@ -59,10 +60,12 @@ public class Session { public static class Info implements Parcelable { public final String sessionId; public final String methodPath; + public final String ownerInfo; - private Info(String id, String path) { + private Info(String id, String path, String owner) { sessionId = id; methodPath = path; + ownerInfo = owner; } public static Info getInfo (Session s) { @@ -70,7 +73,28 @@ public class Session { // not get multiple stacking external sessions (unless we have DEBUG level logging or // lower). return new Info(s.getFullSessionId(), s.getFullMethodPath( - !Log.DEBUG && s.isSessionExternal())); + !Log.DEBUG && s.isSessionExternal()), s.getOwnerInfo()); + } + + public static Info getExternalInfo(Session s, @Nullable String ownerInfo) { + // When creating session information for an existing session, the caller may pass in a + // context to be passed along to the recipient of the external session info. + // So, for example, if telecom has an active session with owner 'cad', and Telecom is + // calling into Telephony and providing external session info, it would pass in 'cast' + // as the owner info. This would result in Telephony seeing owner info 'cad/cast', + // which would make it very clear in the Telephony logs the chain of package calls which + // ultimately resulted in the logs. + String newInfo = ownerInfo != null && s.getOwnerInfo() != null + // If we've got both, concatenate them. + ? s.getOwnerInfo() + "/" + ownerInfo + // Otherwise use whichever is present. + : ownerInfo != null ? ownerInfo : s.getOwnerInfo(); + + // Create Info based on the truncated method path if the session is external, so we do + // not get multiple stacking external sessions (unless we have DEBUG level logging or + // lower). + return new Info(s.getFullSessionId(), s.getFullMethodPath( + !Log.DEBUG && s.isSessionExternal()), newInfo); } /** Responsible for creating Info objects for deserialized Parcels. */ @@ -80,7 +104,8 @@ public class Session { public Info createFromParcel(Parcel source) { String id = source.readString(); String methodName = source.readString(); - return new Info(id, methodName); + String ownerInfo = source.readString(); + return new Info(id, methodName, ownerInfo); } @Override @@ -100,6 +125,7 @@ public class Session { public void writeToParcel(Parcel destination, int flags) { destination.writeString(sessionId); destination.writeString(methodPath); + destination.writeString(ownerInfo); } } @@ -206,6 +232,14 @@ public class Session { return Info.getInfo(this); } + public Info getExternalInfo(@Nullable String ownerInfo) { + return Info.getExternalInfo(this, ownerInfo); + } + + public String getOwnerInfo() { + return mOwnerInfo; + } + @VisibleForTesting public String getSessionId() { return mSessionId; diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index ac300587cef8..67e5eabf54eb 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -16,6 +16,7 @@ package android.telecom.Logging; +import android.annotation.Nullable; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -180,7 +181,7 @@ public class SessionManager { Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId, sessionInfo.methodPath, System.currentTimeMillis(), - false /*isStartedFromActiveSession*/, null); + false /*isStartedFromActiveSession*/, sessionInfo.ownerInfo); externalSession.setIsExternal(true); // Mark the external session as already completed, since we have no way of knowing when // the external session actually has completed. @@ -224,7 +225,7 @@ public class SessionManager { // Start execution time of the session will be overwritten in continueSession(...). Session newSubsession = new Session(threadSession.getNextChildId(), threadSession.getShortMethodName(), System.currentTimeMillis(), - isStartedFromActiveSession, null); + isStartedFromActiveSession, threadSession.getOwnerInfo()); threadSession.addChild(newSubsession); newSubsession.setParentSession(threadSession); @@ -238,12 +239,18 @@ public class SessionManager { return newSubsession; } + public synchronized Session.Info getExternalSession() { + return getExternalSession(null /* ownerInfo */); + } + /** * Retrieve the information of the currently active Session. This information is parcelable and * is used to create an external Session ({@link #startExternalSession(Session.Info, String)}). * If there is no Session active, this method will return null. + * @param ownerInfo Owner information for the session. + * @return The session information */ - public synchronized Session.Info getExternalSession() { + public synchronized Session.Info getExternalSession(@Nullable String ownerInfo) { int threadId = getCallingThreadId(); Session threadSession = mSessionMapper.get(threadId); if (threadSession == null) { @@ -251,8 +258,7 @@ public class SessionManager { "active."); return null; } - - return threadSession.getInfo(); + return threadSession.getExternalInfo(ownerInfo); } /** diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index 05480dc38a0d..f947d34e5baf 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -30,13 +30,16 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.telecom.Logging.Session; import android.view.Surface; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * A connection provided to a {@link ConnectionService} by another {@code ConnectionService} @@ -655,6 +658,8 @@ public final class RemoteConnection { private int mCallerDisplayNamePresentation; private RemoteConference mConference; private Bundle mExtras; + private String mCallingPackage; + private String mCallingPackageAbbreviation; /** * @hide @@ -667,6 +672,13 @@ public final class RemoteConnection { mConnectionService = connectionService; mConnected = true; mState = Connection.STATE_INITIALIZING; + if (request != null && request.getExtras() != null + && request.getExtras().containsKey( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) { + mCallingPackage = request.getExtras().getString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME); + mCallingPackageAbbreviation = Log.getPackageAbbreviation(mCallingPackage); + } } /** @@ -705,6 +717,8 @@ public final class RemoteConnection { Bundle newExtras = new Bundle(); newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); putExtras(newExtras); + mCallingPackage = callingPackage; + mCallingPackageAbbreviation = Log.getPackageAbbreviation(mCallingPackage); } /** @@ -899,11 +913,14 @@ public final class RemoteConnection { * Instructs this {@code RemoteConnection} to abort. */ public void abort() { + Log.startSession("RC.a", getActiveOwnerInfo()); try { if (mConnected) { - mConnectionService.abort(mConnectionId, null /*Session.Info*/); + mConnectionService.abort(mConnectionId, Log.getExternalSession()); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -911,11 +928,14 @@ public final class RemoteConnection { * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer. */ public void answer() { + Log.startSession("RC.an", getActiveOwnerInfo()); try { if (mConnected) { - mConnectionService.answer(mConnectionId, null /*Session.Info*/); + mConnectionService.answer(mConnectionId, Log.getExternalSession()); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -925,11 +945,14 @@ public final class RemoteConnection { * @hide */ public void answer(int videoState) { + Log.startSession("RC.an2", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.answerVideo(mConnectionId, videoState, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -937,11 +960,14 @@ public final class RemoteConnection { * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject. */ public void reject() { + Log.startSession("RC.r", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.reject(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -949,11 +975,14 @@ public final class RemoteConnection { * Instructs this {@code RemoteConnection} to go on hold. */ public void hold() { + Log.startSession("RC.h", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.hold(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -961,11 +990,14 @@ public final class RemoteConnection { * Instructs this {@link Connection#STATE_HOLDING} call to release from hold. */ public void unhold() { + Log.startSession("RC.u", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.unhold(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -973,11 +1005,15 @@ public final class RemoteConnection { * Instructs this {@code RemoteConnection} to disconnect. */ public void disconnect() { + Log.startSession("RC.d", getActiveOwnerInfo()); try { if (mConnected) { - mConnectionService.disconnect(mConnectionId, null /*Session.Info*/); + mConnectionService.disconnect(mConnectionId, Log.getExternalSession( + mCallingPackageAbbreviation)); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -991,11 +1027,14 @@ public final class RemoteConnection { * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. */ public void playDtmfTone(char digit) { + Log.startSession("RC.pDT", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.playDtmfTone(mConnectionId, digit, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1007,11 +1046,14 @@ public final class RemoteConnection { * currently playing, this method will do nothing. */ public void stopDtmfTone() { + Log.startSession("RC.sDT", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.stopDtmfTone(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1037,12 +1079,16 @@ public final class RemoteConnection { * @param proceed Whether or not to continue with the post-dial sequence. */ public void postDialContinue(boolean proceed) { + Log.startSession("RC.pDC", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.onPostDialContinue(mConnectionId, proceed, null /*Session.Info*/); } } catch (RemoteException ignored) { + // bliss + } finally { + Log.endSession(); } } @@ -1052,11 +1098,14 @@ public final class RemoteConnection { * See {@link Call#pullExternalCall()} for more information. */ public void pullExternalCall() { + Log.startSession("RC.pEC", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.pullExternalCall(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1079,12 +1128,15 @@ public final class RemoteConnection { * @param state The audio state of this {@code RemoteConnection}. */ public void setCallAudioState(CallAudioState state) { + Log.startSession("RC.sCAS", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.onCallAudioStateChanged(mConnectionId, state, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1095,12 +1147,15 @@ public final class RemoteConnection { * @hide */ public void startRtt(@NonNull Connection.RttTextStream rttTextStream) { + Log.startSession("RC.sR", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.startRtt(mConnectionId, rttTextStream.getFdFromInCall(), rttTextStream.getFdToInCall(), null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1110,11 +1165,14 @@ public final class RemoteConnection { * @hide */ public void stopRtt() { + Log.startSession("RC.stR", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.stopRtt(mConnectionId, null /*Session.Info*/); } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1128,6 +1186,7 @@ public final class RemoteConnection { * the in-call app. */ public void sendRttUpgradeResponse(@Nullable Connection.RttTextStream rttTextStream) { + Log.startSession("RC.sRUR", getActiveOwnerInfo()); try { if (mConnected) { if (rttTextStream == null) { @@ -1140,6 +1199,8 @@ public final class RemoteConnection { } } } catch (RemoteException ignored) { + } finally { + Log.endSession(); } } @@ -1164,6 +1225,22 @@ public final class RemoteConnection { return mConference; } + /** + * Get the owner info for the currently active session. We want to make sure that any owner + * info from the original call into the connection manager gets retained so that the full + * context of the calls can be traced down to Telephony. + * Example: Telecom will provide owner info in it's external session info that indicates + * 'cast' as the calling owner. + * @return The active owner + */ + private String getActiveOwnerInfo() { + Session.Info info = Log.getExternalSession(); + if (info == null) { + return null; + } + return info.ownerInfo; + } + /** {@hide} */ String getId() { return mConnectionId; diff --git a/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java new file mode 100644 index 000000000000..6c6375586225 --- /dev/null +++ b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.Telephony; +import android.text.TextUtils; +import android.util.Log; + +/** + * This class provides utility functions related to CellBroadcast. + */ +public class CellBroadcastUtils { + private static final String TAG = "CellBroadcastUtils"; + private static final boolean VDBG = false; + + /** + * Utility method to query the default CBR's package name. + */ + public static String getDefaultCellBroadcastReceiverPackageName(Context context) { + PackageManager packageManager = context.getPackageManager(); + ResolveInfo resolveInfo = packageManager.resolveActivity( + new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION), + PackageManager.MATCH_SYSTEM_ONLY); + String packageName; + + if (resolveInfo == null) { + Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: no package found"); + return null; + } + + packageName = resolveInfo.activityInfo.applicationInfo.packageName; + + if (VDBG) { + Log.d(TAG, "getDefaultCellBroadcastReceiverPackageName: found package: " + packageName); + } + + if (TextUtils.isEmpty(packageName) || packageManager.checkPermission( + android.Manifest.permission.READ_CELL_BROADCASTS, packageName) + == PackageManager.PERMISSION_DENIED) { + Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: returning null; " + + "permission check failed for : " + packageName); + return null; + } + + return packageName; + } +} diff --git a/wifi/Android.bp b/wifi/Android.bp index 6a8600a5baa4..1e2c81a60178 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -143,6 +143,16 @@ droidstubs { "framework-module-stubs-defaults-publicapi", "framework-wifi-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-wifi.api.public.latest", + removed_api_file: ":framework-wifi-removed.api.public.latest", + }, + api_lint: { + new_since: ":framework-wifi.api.public.latest", + baseline_file: "api/lint-baseline.txt", + }, + }, } droidstubs { @@ -151,6 +161,16 @@ droidstubs { "framework-module-stubs-defaults-systemapi", "framework-wifi-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-wifi.api.system.latest", + removed_api_file: ":framework-wifi-removed.api.system.latest", + }, + api_lint: { + new_since: ":framework-wifi.api.system.latest", + baseline_file: "api/system-lint-baseline.txt", + }, + }, } droidstubs { @@ -159,6 +179,15 @@ droidstubs { "framework-module-api-defaults-module_libs_api", "framework-wifi-stubs-srcs-defaults", ], + check_api: { + last_released: { + api_file: ":framework-wifi.api.module-lib.latest", + removed_api_file: ":framework-wifi-removed.api.module-lib.latest", + }, + api_lint: { + new_since: ":framework-wifi.api.module-lib.latest", + }, + }, } droidstubs { diff --git a/wifi/api/lint-baseline.txt b/wifi/api/lint-baseline.txt new file mode 100644 index 000000000000..892411f8c3a1 --- /dev/null +++ b/wifi/api/lint-baseline.txt @@ -0,0 +1,13 @@ +// Baseline format: 1.0 +GenericException: android.net.wifi.WifiManager.LocalOnlyHotspotReservation#finalize(): + Methods must not throw generic exceptions (`java.lang.Throwable`) +GenericException: android.net.wifi.WifiManager.MulticastLock#finalize(): + Methods must not throw generic exceptions (`java.lang.Throwable`) +GenericException: android.net.wifi.WifiManager.WifiLock#finalize(): + Methods must not throw generic exceptions (`java.lang.Throwable`) + + +VisiblySynchronized: PsiThisExpression:WifiManager.this: + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.net.wifi.WifiManager.WifiLock.finalize() +VisiblySynchronized: android.net.wifi.WifiManager.WifiLock#finalize(): + Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.net.wifi.WifiManager.WifiLock.finalize() diff --git a/wifi/api/system-lint-baseline.txt b/wifi/api/system-lint-baseline.txt new file mode 100644 index 000000000000..6547ee8a2188 --- /dev/null +++ b/wifi/api/system-lint-baseline.txt @@ -0,0 +1,6 @@ +// Baseline format: 1.0 +MissingGetterMatchingBuilder: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig): + android.net.wifi.rtt.RangingRequest does not declare a `getResponders()` method matching method android.net.wifi.rtt.RangingRequest.Builder.addResponder(android.net.wifi.rtt.ResponderConfig) + +MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig): + |